天天看點

[轉]如何分離個人資訊,緩存動态頁面

如何分離個人資訊,緩存動态頁面

肖理達 (KrazyNio AT hotmail.com), 2005.06.07, 轉載請注明出處

一直想寫一篇關于動态頁面 cache 的文章,但每次“提筆”卻又放棄,因為總是覺得準備得還不夠充分。今天埋頭寫下,隻是希望對自己的工作做一些筆錄。

1、問題起源

我們經常會在一個動态頁面中加入很多個人資訊,以 CMS 首頁為例,使用者登入之前顯示登入框,登入之後顯示其使用者名,并根據權限顯示其可用子產品的連結。由于每個使用者登入之後,顯示出來的動态資訊都是不一樣的,是以這部分無法進行 cache,我們将這部分資訊定義為“ 個人資訊”,它的特性是根據登入使用者進行動态改變。

現在問題來了,就是一個 CMS 的首頁,通路者的登入機率并不是百分百的,應該說有一大部分人通路首頁是沒有登入的,這個時候的首頁是一個公共的頁面,沒有任何個人資訊,或者說這時候首頁的任何動态資訊都是可以轉換成靜态的,也就是說這部分是可 cache 的。

2、使用 JavaScript 分離個人資訊

解決這個問題的方法有很多種,一種是将個人資訊和其他資訊進行分離,如在 CMS 首頁中加入一個外部的 JavaScript 檔案,而這個檔案的内容實際上是由 PHP 動态生成的。CMS 首頁 index.php 代碼片段:

<script language="JavaScript" src="/js/personal.php"></script>
....
<script language="JavaScript">
document.write(sUser);
document.write(sLinks);
</script>
      

personal.php 代碼片段:

<?php
session_start();
header("Cache-Control: no-store, no-cache, must-revalidate");
?>
sUser = "<?php echo $_SESSION['USER']; ?>";
sLinks = "<?php echo addslashes($_SESSION['LINKS']); ?>";      

這種方法比較适合個人資訊較少,易于集中顯示的情況。通過 JavaScript 外挂代碼實作個人資訊分離之後,personal.php 是永不 cache 的,這樣也就可以放心地對 CMS 首頁進行 cache 了,具體的 cache 方法可采用 304 HTTP 頭與 Cache_Lite 相結合的方式,這在後邊有詳細代碼示例。

3、選擇性分離個人資訊

一旦個人資訊較多,很難對其進行分離的時候,上述方法實作起來就會比較麻煩了。接下來介紹一種比較放寬的 cache 方式,就是隻對使用者未登入的 CMS 首頁進行 cache,一旦發現使用者處于登入狀态,則跳過 cache 部分,直接運作相關代碼,我把這種方法稱為“ 選擇性 cache”。這種方法中,我們犧牲了一部分可 cache 的情況,但很大程度上提高了個人資訊的可擴充性,也就是說個人資訊的多少、顯示的位置等等都不再受到限制,這是值得的,畢竟修改服務端代碼要比修改大量的 JavaScript 代碼要來得友善,而且 JavaScript 的調試也會比較麻煩。

分析一下這種 cache 方式,概要流程圖如下:

[轉]如何分離個人資訊,緩存動态頁面

代碼片段如下:

<?php
session_cache_limiter("must-revalidate"); 
session_start();
if (empty($_SESSION['USER'])) { //未登入時才使用 cache
    ....    //輸出 cache
} else {
    //  直接輸出頁面内容,此條件下與未使用 cache 時一樣
    $data =& get_data();
    echo $data;
}   //end if 
?>      

這裡需要說明的一點是,由于 PHP 在使用 SESSION 時,php.ini 中預設設定的 session.cache_limiter 為 nocache,是以需要修改 cache_limiter,設定成 must-revalidate,使得用戶端再次浏覽目前頁時必須發送相關 HTTP 頭資訊到伺服器進行驗證,然後才決定是否加載用戶端本地 cache。不要把用戶端本地 cache 與伺服器端 cache 搞混,之後的代碼片段中會充分利用用戶端 cache 和 伺服器端 cache 機制達到緩存的目的。關于 must-revalidate,請參考 HTTP 規格說明書 RFC 2612 的 14.9.4 章節。

上邊的代碼并不完整,接下來是更加深入的探讨用戶端 cache 機制了,利用用戶端 cache,可以有效地減輕伺服器端負載。首先了解一下 HTTP 頭:Last-Modified 與 If-Modified-Since。簡單的說,Last-Modified 與If-Modified-Since 都是用于記錄頁面最後修改時間的 HTTP 頭資訊,隻是 Last-Modified 是由伺服器往用戶端發送的 HTTP 頭,而 If-Modified-Since 則是由用戶端往伺服器發送的頭,其工作原理圖如下:

[轉]如何分離個人資訊,緩存動态頁面

可以看到,再次請求本地存在的 cache 頁面時,用戶端會通過 If-Modified-Since 頭将先前伺服器端發過來的 Last-Modified 最後修改時間戳發送回去,這是為了讓伺服器端進行驗證,通過這個時間戳判斷用戶端的頁面是否是最新的,如果不是最新的,則傳回新的内容,如果是最新的,則傳回 304 告訴用戶端其本地 cache 的頁面是最新的,于是用戶端就可以直接從本地加載頁面了,這樣在網絡上傳輸的資料就會大大減少,同時也減輕了伺服器的負擔。想要詳細檢視 HTTP 頭資訊,可以在 Firefox 中安裝 LiveHTTPHeaders 插件,安裝完成之後按 Alt+L 就可以在 Sidebar 中看到了。

現在再來完善之前的 index.php 代碼:

<?php
session_cache_limiter("must-revalidate"); 
session_start();

function &get_data()
{
    //此函數用于擷取本頁面的輸出内容
    //....
}   //end function

if (empty($_SESSION['USER'])) { //未登入時才使用 cache
    //====================================================
    //  1. 檢查 HTTP 頭是否符合 304 的條件
    //====================================================
    //get_last_modified() 函數需要另外單獨實作,此函數用于擷取伺服器端 cache 檔案的最後修改時間,可将時間戳儲存在資料庫中。
    $last_modified = get_last_modified();
    $headers = getallheaders();
    if (strtotime($headers['If-Modified-Since']) == $last_modified) {
        //  傳回 304 并結束程式運作
        header('HTTP/1.1 304 Not Modified');
        exit;
    }   //end if
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $last_modified) . ' GMT');
    //====================================================
    //  2. 檢查 cache 檔案是否存在
    //====================================================  
    require_once 'Cache/Lite.php';
    //  參數設定
    $options = array(
        'cacheDir' => './cache/',
        'lifeTime' => 86400, //最大 cache 一天時間
        'fileNameProtection'   => false //使用 CMS 自身提供的 id 作為名字
    );
    $cache = new Cache_Lite($options);  //建立 Cache_Lite 對象
    $id = md5($_SERVER['REQUEST_URI']); //生成對應于 cache 檔案的 ID
    if ($data = $cache->get($id)) {   //存在 cache 檔案,擷取内容,直接輸出
        echo $data;
    } else {
        $data =& get_data();
        echo $data;
        flush();
        $cache->save($data);    //儲存 cache
    }   //end if
} else {
    $data =& get_data();
    echo $data;
}   //end if
?>       

測試時可以使用 LivHTTPHeaders 插件,你将會看到第一次通路時是傳回 200,第二次到第N次通路時則傳回了 304,而登入之後,則一直都傳回 200,因為我們選擇性 cache 之後,對登入之後一律運作程式輸出,而不使用 cache,如果之後需要對輸出的個人資訊進行修改,隻需要改函數 get_data() 即可,也避免了 JavaScript 的調試。

總結

除此之外,還有其它方法可以實作分離個人資訊,緩存動态頁面的目的。而且為了提高伺服器運作效率,還可以使用資料庫 cache、Squid 反向代理等,如 ADOdb 的 cache。目前用的比較多的 Drupal 應用的就是本文中提到的第二種方法。

參考資料

HTTP Caching & Cache-Busting for Content Publishers

Hypertext Transfer Protocol -- HTTP/1.1

PHP Anthology, Volume 2: Applications. Chapter 5: Caching

Caching Tutorial for Web Authors and Webmasters

Drupal 源代碼 //from: http://www.infor96.com/~nio/comments.php?id=287_0_1_50_C1

繼續閱讀