HTTP緩存

緩存的作用

簡言之,就是加快訪問速度,節約帶寬。這是基於這樣一個事實,很多重複訪問的網頁在一段時間(幾秒到幾天,甚至幾個月)內保持不變。把之前訪問的副本保存起來,下次訪問同一個文檔時,直接使用緩存中的副本作爲響應。這樣就不會有網絡時延,不會有流量的消耗。能夠給用戶提供即時體驗。

要解決的問題

緩存哪些文檔?緩存多久?怎樣確定緩存的副本是有效的?

緩存哪些文檔?

一般而言,哪些文檔能夠緩存,哪些不應該緩存,主要是由服務器指定的。客戶端的策略主要是基於服務器的響應。服務器又是通過響應中的一些頭部域通告客戶端的。與緩存相關的頭部域:

域名
說明
Date
原始服務器產生響應的日期,中間的代理和緩存一定不能修改這個日期
Age
 當代理服務器用自己緩存的副本響應請求時,用該頭部域表明該副本從產生到現在經過多長時間了。文檔經過代理時,Age首部值會隨之增加
Expires
指定響應資源的過期日期,表示在這個時間之前都是有效的
Cache-Control
有很多參數,看下面
:max-age=<s>
定義文檔的最大使用期
:no-store
資源不能被持久化保存起來(可以放入緩存),只能存在於內存中。用於防止敏感性的數據被複制。
:no-cache
不能被緩存起來。
:must-revalidate
每次在使用副本時,都要和源服務器進行驗證確定是否爲最新數據。
:private
數據資源只能被存儲到私有的caches中,即不能存儲在中間緩存服務器中
:public
數據資源可以在任何地方緩存起來
Last-Modified
資源文檔最後被修改的日期
Etag
 與資源文檔關聯的一個值,類似於資源文檔的MD5值
  
 上表是響應中用到的,請求中也可能包含Cache-Control域,用於限制可以接收的響應。

緩存多久?

緩存多久與資源的新鮮生存期(freshness lifetime, 最大使用期)有關,其實叫保質期更好理解。指的是已緩存文檔在不能提供給客戶端使用之前能夠存在的時間長度。過了保質期的文檔就不是最新的了,不能直接提供給客戶端使用。計算方法如下:

int freshness_limit ()
{
        int heuristic , server_freshness_limit, time_since_last_modified ;
        heuristic = 0;

        if (Max_Age_value_set )
       {
               server_freshness_limit = Max_Age_value ;
       }
        else if ( Expire_value_set)
       {
               server_freshness_limit = Expire_value - Date_value;
       }
        else if ( Last_Modified_value_set)
       {
               time_since_last_modified = max (0, Date_value - Last_Modified_value);
               server_freshness_limit = int ( time_since_last_modified* lm_factor );
               heuristic = 1;
       }
        else
       {
               server_freshness_limit = default_cache_min_lifetime ;
               heuristic = 1;
       }

        if (heuristic )
       {
               if (server_freshness_limit > default_cache_max_lifetime)
                      server_freshness_limit = default_cache_max_lifetime ;
               else if ( server_freshness_limit < default_cache_min_lifetime )
                      server_freshness_limit = default_cache_min_lifetime ;
       }
        return 0;
}
如果響應中含有Cache-Control: max-age=<s>,則保質期就等於max-age的值<s>,單位是秒。
否則,如果響應中含有Exprires,則保質期就等於Expires的值減去Date的值
否則,如果響應中含有Last-Modified,則求出一個試探性最大使用期(LM-Factor算法)
否則,採用一個默認值
後面的if語句,是對試探性值進行修正。

LM-Factor算法

如果響應中沒有Cache-Control: max-age首部,也沒有Expires首部使用,就計算出一個試探性最大使用期。


time_since_modify = max(0, server_Date - server_Last_Modified);
server_freshness_limit = int(time_since_modify * lm_factor);

lm_factor的值可以是0.2或其它
通常會爲試探性新鮮週期設置上限,一天或一週。
如果最後修改日期也沒有的話,緩存就沒什麼信息可以利用了。這種情況下,通常的做法是分配一個默認新鮮週期,通常是1小時或1天。有時,比較保守的做法是設置爲0.

怎樣確定緩存的副本是有效的?

爲了確定一個副本是否還是新鮮的,只需要計算兩個值:文檔使用期(age)和新鮮生存週期(freshness_limit)
如果 age < freshness_limit,則副本還是新鮮的,freshness_limit的計算前面已提到。

文檔使用期

服務器發佈響應後經過的總時間。使用期包含了響應在因特網路由器和網關中游蕩的時間,在中間節點緩存中存儲的時間,以及響應在你的緩存中停留的時間。
直觀來講,age的值應該等於current_time - Date_header_value(響應中Date域的值)。但有一個問題,不能保證服務器與客戶端的時間是同步的,可能相差幾秒,幾分鐘,甚至幾小時,也可能更大。
HTTP用一些魔法對時鐘偏差和網絡時延進行了補償。具體的計算方法如下:

int age ( int time_got_response, int Date_header_value , int Age_header_value)
{      
        int time_issued_request , current_time;
              
        /* 修正時鐘偏差造成的負數使用期 */
        int apparent_age = max(0, time_got_response - Date_header_value );
        /* 修正時鐘偏差,取最保守的值 */
        int corrected_apparent_age = max( apparent_age, Age_header_value );
        /* 對網絡時延的補償 */
        int response_delay_estimate = ( time_got_response - time_issued_request );
        int age_when_document_arrived_at_our_cache = corrected_apparent_age + response_delay_estimate ;
        int how_long_copy_has_been_in_our_cache = current_time - time_got_response ;
        int age = age_when_document_arrived_at_our_cache + how_long_copy_has_been_in_our_cache ;

        return age ;

}

計算出來的值實際上可能會比較保守,但保守總低估好(低估可能會把已經過期的文檔當做還是新鮮的,從而顯示了錯誤的信息)。corrected_apparent_age的值可能會極大地低估文檔的使用期。回憶一下,Age的值並不包括網絡時延,如果corrected_apparent_age的值取得是Age值,那它的值就可能比實際的文檔使用期低很多,本着保守總比低估好的原則,我們應該把網絡時延考慮進去。這就形成了上面的算法。詳情可參考《HTTP權威指南》。

其它思考

與緩存性能有關的因素:緩存策略,淘汰算法,緩存空間大小,用戶使用習慣。採用自適應算法可能可以大大改善緩存的性能。即根據用戶的使用歷史做一些個性化的調整。比如根據以往的訪問歷史制定淘汰策略,根據使用習慣調整緩存大小等。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章