4/10/2009

前端页面优化-HTTP/1.1协议中缓存处理和web服务器实现(一)

HTTP/1.1协议中缓存处理和web服务器实现

为了提供网站性能,很多地方都使用到缓存。此文章 主要从HTTP协议出发,谈论了
其缓存规范,以及在流行的Web服务器:IIS和Apache中是如何实现的。

HTTP/1.1 中缓存的目的:首先为了降低发送请求,其次是降低发送完 整响应。前者使HTTP请求/响应中往返流程减少,我们用"过期模型"来达到这 一目的(见13.2 节);后者使网络带宽需求降低,我们用"验证模型"机制来 达到这一目的(见13.3 节)。


在介绍这两个模型前,先让我们看下一个普通的 HTTP请求/响应流程。
HTTP请求资源大体流程过程如下:
1.
用户代理向源服务器发送第一次请求。
2.
源服务器进行响应,在第一次响应过程中会将一些报头域加入响应消息 中,其中和缓存相关的有:Age,Date,Expires,Cache-Control,Last-Modified,Etag等。
3.
当用户代理进行第二次请求时,浏览器会首请求资源的本地缓存,并且进 行检测看缓存是否过期。
检测方法:根据报头域Expires或者Cache-Control:max- age和实体年龄(并不是报头域Age,而是以Age等为基础计算得值。计算方法见下文)进行比较。
a)
如果缓存存在并 且是保鲜的,则不再向源服务器发送请求(减少了请求数,实现了第一个目的)。
b)
如果缓存过期,则用户代理会重新发送请求。发送时会将Last- Modified(如果存在)的值放入到If-Modified-Since/If-Unmodified-Since中,或者将Etag(如果存在)的 值放入到If-None-Match/If-Match中,从而形成两个报头域,用于源服务器的验证。
c)
如果缓存不存在(比如删除了IE临时文件),则用户代理会重新发送请 求,就和第一次发送请求的情况相同。
4.
源服务器再次接收到请求后,首先会检测并且验证请求报头域,检测内容 主要是看是否包含If-Modified-Since、If-Unmodified-Since、If-None-Match、If-Match等。
验证方法:
比较用户代理报头域中If-Modified-Since/If- Unmodified-Since的值和源服务器上文件的Last-Modified的值,检测文件是否更改过。
比较用户代理报头域中If-None-Match/If-Match的值和源服务器 上文件的ETag的值,检测是否相同。
a)
如果存在,并 且验证成功,则直接返回304(未更改)状态码,而不重新发送实体内容(节省了网络流量,实现了第二个目的)。
b)
如果不存在或 者验证不成功,则源服务器会重新读取实体,将新的内容发送给用户代理(同时附加新的报头域?)。
例外:如果用户直接点击刷新按钮,用户代理将不进行本地缓存的检测,将直接进入如上 所述步骤3-b,4的请求流程。
其大体流程图如下:



附件: 您所在的用户组无法下载或查看附件


在HTTP请求处理流程中,步骤3 使用的是过期模型,步骤4使用的是验证模型。下面是对这两个模型的简要介绍。两个模型的内容来自HTTP/1.1协议,但是为了便于理解,进行了一定程度 的删改。(详见参 考文档7


过期模型(Expiration Model)

过期模型主要是指浏览器(客户代理User Agent)中的缓存处理机制。其主要原理为:源服务器在响应时会给实体加上相应的Date,以Date和客户端时间等一系列相关报头域计算出该实体年龄 (Age/Current_Age)。客户代理在请求时会将计算出的Age和Cache-Control:Max-age或者Expires进行比较,如 果没有年龄/没有超过最大年龄,或者没有超过过期时间,则认为该实体仍然是保鲜的,否则就认为其为过期的。

年 龄计算:


为了解缓存实体是否为最新,缓存器 需要知道其年龄是否已超过保鲜时限。我们在13.2.4 节中讨论如何计算后者,本节讨论如何计算响应或缓存实体的年龄。


在此讨论中我们用“now”来 表示主机进行计算时时钟的当前值


使用HTTP 协 议的主机,除了运行源服务器和缓存器的主机,应当使用NTP[28]或其他类似协议来将其时钟同步到一个全球性的精确时间标准上来。


HTTP1.1
协议要求源服务器尽可能在发 送每条响应时都附加一个日期。报头来标明此响应产生的时间.(14.18)我们用"日期值"这一短语来表示日期报头的值----一种适于算术操作的形式.


当从缓存获得响应消息时,HTTP1.1 用年龄响应报头来传达其年龄信息


年龄值是缓存器估计的源服务器生成或重新确认响应的时间值本质上,年龄值 是响应信息在从源服务器开始的所有缓存器驻留的时间加上其在网络路径上传输的时间.


我们用"age_value"来标明年龄报头的值----一种适于算术操作的表示方法.


一个响应的年龄可以通过两种完全独立的途径来计算:

1.
如果本地时 钟与源服务器时钟同步的相当好,则用"now"-日期值,若结果为负,则取零.

2.
如果从源服 务器开始的所有缓存器均执行HTTP1.1 则就取age_value


如上我们有两种方法计算响应的年龄,我们合并二者如下:


corrected_received_age = max(now - date_value, age_value)



无论那种方法都能得到可靠的结果由于网络附加延时,一些重要时隙会在服务器产生响应和下一个缓存器或客户 收到它之间被忽略如果不经修订,这一延迟会带来不正常的低年龄.

corrected_initial_age = corrected_received_age+ (now - request_time)


因为导致返回年龄值的请求一定在年龄值的产生之前就发出了,我们可以通过记录请求发出的时间来矫 正网络附加延时。因此当收到一个年龄值时,它必须与发出的请求时间有关,而于收到的响应时间无关。这将保证不论经历多少延时,其表现都是稳定的。


当缓存收到响应时的算法摘要:

/*

* age_value

* is the value of Age: header received by the cache with

* this response.

* date_value

* is the value of the origin server's Date: header

* request_time

* is the (local) time when the cache made the request

* that resulted in this cached response

* response_time

* is the (local) time when the cache received the

* response

* now

* is the current (local) time

*/

apparent_age = max(0, response_time - date_value);

/*
response_time = now?
*/

corrected_received_age = max(apparent_age, age_value);

response_delay = response_time - request_time;

corrected_initial_age = corrected_received_age + response_delay;

resident_time = now - response_time;

current_age = corrected_initial_age + resident_time;


以上段落英文更明白一些。

缓存实体的当前年龄是从缓存实体最后被服务器确认的时间(以秒 记)加上校正初始年龄。当缓存实体产生一条响应,它必须包含一个年龄报头区与缓存实体当前年龄一样的值。


注:年龄的计算公式看的比较晕,主要不明白各个时间点,以及相应的时间计算。

过期计算:

为了确定一条响应是新是旧,我们需要将其保鲜期限(freshness_lifetime)和年龄进行比较(年龄计算见)本节讲解怎样计算保鲜期限,以及一条响应是否已经被排出。

freshness_lifetime计算方式为:

freshness_lifetime = max_age_value(Cache-Control:Max-Age/s- Max-Age值)

或者

freshness_lifetime = expires_value(Expires域)- date_value(Date域)

注:Max-Age优先于Expires进行计算


如果 Expires, Max-Age, s-Max-Age均未在响应中出现,且响应对缓存没有其他限制, 缓存可以用启发式算法计算freshness_lifetime。如果响应有最后修改时间,启发式过期值应不大于时间片。 典型设置为片断的10%

freshness_lifetime =(last_modified_value – date_value)* 10%


此公式在协议中并未出现,是根据协议内容推理出的。其他的启发 式算法HTTP协议也没有给出换句话说:即 使不设置Expires, Max-Age,IE缓存器 也会使用启发式算法来设置?此想法需要讨论。


接下来计算响应是否过期就非常简单:

response_is_fresh = (freshness_lifetime > current_age)。不用过多解释吧o(∩_∩)o…


验证模型(Validation Model)

当用户代理想要用一个失时效的条目来响应客户请求,他首先必须向源服务器(也可能是中间缓存器)检验这一缓存条目是否仍然可用.我们称之为"验证"缓存条目.

由于我们不想当缓存条目为可用时必须为再传送整条响应而付出代价而且不想当缓存条 目不可用时也必须多传一圈HTTP1.1 协议支持使用条件反应方法协议支持条件响应 方法的关键特征围绕"缓存验证器"展开当源服务器生成一个 完整响应时它同时传送一类验证器一直伴随着缓存条目当一客户(用户代理或代理缓存器)对含有缓存条目的资源做出条件请求时他同时在请求中包 含有相互关联的验证器.


服务器则核对此验证器和当前验证器如果他们匹配(13.3.3)则返回一个特定状态码(通常为304)且不含条目内容否则返回整个响应(包含条目内容)这样我们避免了在验证器 匹配时传送整条响应同时也避免了在不匹配时往返传输

HTTP/1.1 协议中一个条件请求除了带 有特别的报头(包含验证器)来暗中的将它转入条件算法以外,和普通报头没有差别

协议中包括缓存确认机制的主动和被动两种状态具体说来请求可以在当且仅 当又匹配确认时执行也可以在当且仅当没有匹配确认时执行


HTTP/1.0只有一种验证 器:Last-Modified

HTTP/1.1有两种验证 器:Last-ModifiedETag


13.3.1 最后修改日期 (Last-Modified Dates)

最后修改报头值经常被用作验证器.简言之,一缓存条目在最后修改期后未经修改则被认为是有效的.

13.3.2 标签缓存验证器(Entity Tag Cache Validators)

没有评论: