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权威指南》。

其它思考

与缓存性能有关的因素:缓存策略,淘汰算法,缓存空间大小,用户使用习惯。采用自适应算法可能可以大大改善缓存的性能。即根据用户的使用历史做一些个性化的调整。比如根据以往的访问历史制定淘汰策略,根据使用习惯调整缓存大小等。



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