4/10/2009

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

标签响应报头值,一个实体标签,提供了一个"模糊"缓存验证器.这将允许在不便存 储修改信息的情况下进行可靠的确认,包括HTTP 日期数据不充足和源服务器不想因使用修改日期而带来麻烦的情况.

实体标签描述见3.11 节,有关其报头的描述 见14.19,14.24,14.26,14.44.


13.3.3 强,弱验证器

由于源服务器和缓存器会比较两个验证器来确定他们是否代 表相同的条目,所以通常

希望实体发生任何变化,验证器也相应变化,这样的验证器为强验证器.

同时还会有这样的情况,服务器倾向于仅在发生重要的语义变化时才改变验证器。在资

源变化时验证器未必变化的称为弱验证器.


ETag通常情况下是强验证,Last-Modified通常情况下是弱验证,但通过相应设置,其验证 强度属性将会改变。


Etag
由于是强验证,其优先权高于Last-Modified。


当两类验证器同时存在时,必须两类条件都为真,否则仍然会从源服务器获取数据。


基于以上所述:缓存优先级或者前后顺序为:Max-Age > Expries > Etag > Last-Modified

这个优先级顺序是用户代理与源服务器处理缓存一个依据。值得注意的是ETag,虽然其为强验证, 但由于性能和同步问题,经常舍去不用,下面会详细讲到。



以上都是基于理论介绍,接下来简单的介绍下上文中涉及的各个报头,及其在服务器中的设置,以及出 现的问题及解决方案。


HTTP协议中各报头信息

Expries

Cache-Control


详见参考文档7


Last-Modified(详见参考 文档7)


文件最后修改时间。一般情况下可以理解为源服务器上文件的Last-Modified属性值。具 体实现和Web服务器以及资源(不仅仅是文件)属性有关。


Last-Modified
由服务器端生成,客户端通过If-Modified-Since或者说If-Unmodified-Since这个条件判断请求 来验证资源是否修改。我们常见的是使用If-Modified-Since


ETag


实体值,可用于比较来自同一资源的不同实体。可以简单理解为资源的版本信息。



Etag
主要为了解决Last-Modified无法解决的一些问题(详见参考文档7,11):

1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅 改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;

2、某些文件修改非常频繁,比如在秒以下的时间内进行修改, (比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到 秒)

3、某些服务器不能精确的得到文件的最后修改时间;


为此,HTTP/1.1引入了Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来 很神秘的编码。
Etag 由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。我们常见的是使用If-None- Match。HTTP/1.1虽然引入了ETag,但是并没有说明其具体生成规则,因此需要各服务器自己实现。但是不管怎样的算法,在服务器端都要进行计 算,都要进行比较,计算和比较就有开销,会带来性能损失。因此为了榨干这一点点性能,不少网站完全把Etag禁用了(比如YUI)。与此对应的是 HTTP/1.1鼓励服务器尽可能的开启Etag^_^


你如何选择要根据自己的实际情况。如果你选择了使用,那么恭喜你,还有下面一个问题需要解决。

问题:


当使用群集、负载均衡时,由于各文件最后更新时间,Web服务器设置等方面的差异,会造成相同实 体的ETag不同。并且由于HTTP协议规定,如果Etag和Last-Modified同时存在,则这两个条件必须同时满足才能返回304错误 ,否则会向源服务器发送获取实体的请求。


MSDN
上说IIS5存在此问题,但在IIS6和后续版本中已经解决。(你最好尝试一下)

解决方法:

1. 使用一种机制使多 台服务器上的相同实体的Etag相同

2. 直接去掉Etag



IIS相关实现及设置


Expries

Cache-Control

有两种方法设置:

1. 使用IIS

IISà属性àHTTP头à自定义 HTTP头à添加

Cache-Control:max-age:3600

Expires:Mon, 09 Jul 2018 05:53:19 GMT


2. 使用 adsutil.vbs(详见参考文档2)

到IIS的AdminScripts目录下去找到adsutil.vbs文件。

cd C:\Inetpub\AdminScripts

比如我们要给根目录下的imags目录添加Expires/Cache-Control,首先要 在metabase中给它加一个节点cscript adsutil.vbs create W3SVC/1/root/images "IisWebVirtualDir"

设置images目录下的文件获得Cache-Control: max-age=60,就这样

csript adsutil.vbs set W3SVC/1/root/images/HttpExpires "D, 0x3c"

设置images目录下的文件获得“Expires: Thu 27 Nov 2008 07:00:00 GMT”,

csript adsutil.vbs set W3SVC/1/root/images/HttpExpires "S, Thu 27 Nov 2008 07:00:00 GMT"

使用Metabase
Explorer

也可以修改

下载地址:


Last-Modified

这个基本不用更改,按默认生成的就好。

ETag


IIS
对Etag的计算算法是ETag = {Filetimestamp:ChangeNumber}, Filetimestamp为文件时间戳,ChangeNumber是metabase的change number

解决多服务器ETag同步方法:

1. 使所有IIS的 ETag设置一致。

设置方法详见参考文档3,4

2. 去掉ETag


除了写ISAPI,暂时没有找到其他IIS下去掉ETag的方法。


Apache相关实现及设置

Expries:

Cache-Control


具体设置见参考文档1

Last-Modified


同IIS

ETag

生成机制为:文件的inode(索引节点)、大小、最后修改时间决定,通过配置可以决定使用哪些 要素来生成。具体配置详见参考文档10

解决多服务器ETag同步方法:

1. 去掉ETag


使用配置 FileETag none


Apache对Etag的判断流 程为:(详见参考文档11)

首先判断是不是弱Etag。


如果不是,进入下面的情况:
强Etag 根据配置文件中的配置来设置Etag值,默认的Apache的FileEtag设置为:FileEtag、INode、Mtime、Size,也就是根据 这三个属性来生成Etag值,他们之间通过一些算法来实现,并输出成hex的格式,相邻属性之间用-分隔,比如:Etag "2e681a-6-5d044840"。这里面的三个段,分别代表了INode,MTime,Size根据算法算出的值的Hex格式。


当然,我们可以改变Apache的FileEtag设置,比如设置成FileEtag Size,那么得到的Etag可能为:Etag "6"


总之,设置了几个段,Etag值就有几个段。(不要误以为Etag就是固定的3段式)


说明

这 里说的都是Apache 2.2里面的Etag实现,因为HTTP/1.1并没有规定Etag必须是什么样的实现或者格式,因此你也可以修改或者完全编写自己的算法得到Etag, 比如"2e681a65d044840",客户端会记住并缓存下这个Etag,下次访问的时候直接拿这个值去和服务器生成的Etag对比。



当其为弱校验(弱Etag)时,重新考虑前面提到的3个问题:

问题1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我 们并不希望客户端认为这个文件被修改了,而重新GET;

解决办法:如果使用强Etag,每次得会要求重新GET页面,如果使用Etag,比方说设置成 FileEtag Size等,就可以忽略MTime造成的Last-Modified时间修改从而影响了If-Modified-Since(IMS)这个校验了。这点和 弱Etag无关。


问题2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N 次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)

解 决办法:如果是这种情况,Apache会自动判断请求时间和修改时间之间的差值,如果小于1s,Apache会认为这个文件在这1秒内可能会再次被修改, 因此生成一个弱Etag(Weak Etag),这个Etag仅仅基于MTime来生成,因此MTime只能精确到s,所以1s内生成的Etag总是一样,这样就避免了使用强Etag造成的 1s内频繁的刷新Cache的情况。(貌似不用Etag,仅仅使用Last-Modified就可以解决,但是这针对的仅仅是修改超级频繁的情况,很多文 件可能同时也使用强Etag验证)。弱Etag以W/开始,比如:W/"2e681a"


问题3、某些服务器不能精确的得到文件的最后修改时间;

解决办法:生成Etag,因为Etag可以综合Inode,MTime和Size,可以避免这个 问题




IIS
的ETag 处理机制应该有相似之处。



至此我们较为全面 的介绍了HTTP/1.1协议中关于缓存的处理,以及用户代理,web服务器等相应实现策略。在进行静态文件处理,资源分离,反向代理过程中,很大程度上 要依赖于HTTP协议中缓存命令及各服务器的实现。可以说是我们进行前端优化的一个理论基础。


题外话:

在写这篇文章过程 中,需要查看HTTP/1.1协议,最先看的是英文版,但是阅读速度较慢,因此从网上搜索中文版,最后找到了三份。三份都翻译的很差。最后只能是相互对照 着来看。这么重要的文档,不明白为什么就没有很正式的官方翻译。






术语解释(详见参考文档7):

请求(Request)



一种HTTP 请求消息,参看 第5 章的定义。

应答(Response)


一种HTTP 应答消息,参看 第6 章的定义。

资源(Resource)


一种网络数据对象或 服务,可以用第3.2 节定义的URI 描述。资源可以以多种表现方式

(例如多种语 言,数据格式,大小和解决方案)或其他不同的途径获得。

实体(Entity)


作为请求或应答的有 效负荷而传输的信息.一个实体包含报头形式的维护信息和消息体形式的内容,由第7 节详述.

客户机(Client)


为发送请求建立连接 的程序.

用户代理(User agent)


初始化请求的客户端 程序.常见的如浏览器,编辑器,蜘蛛(网络穿越机器人),或其他的终端用户工具.

服务器(Server)


同意连接以便通过发 回应答为请求提供服务的应用程序.任何给定的程序都有可以既做客户端又做服务器;我们使用这些术语仅指特定连接中程序完成的任务,而不是指通常意义上程序的性能.同样,任何服务器都可以基于每个请求的性质扮演原服务器,代理,网管,或者隧道等诸角色之一。

原服务器(Origin server)


给定的资源驻留或创 建的地方.
保鲜 (Fresh)

如果一个应答的年 龄还没有超过保鲜寿命,它就是保鲜的.

陈旧 (Stale)

一个应答的年龄已 经超过了它的保鲜寿命,就是陈旧的.


参考文档:
1.
缓存友好的网页


http://shiningray.cn/cache-friendly-web-pages.html


2.
Config HTTP Header For Better Client Performance


http://hi.baidu.com/xletian/blog/item/756dcfce4ae17f0692457ed7.html


http://morganchengmo.spaces.live.com/blog/cns!9950CE918939932E!2132.entry


3.
The performance of a Web application may decrease, and the network bandwidth may increase after you add a Web server that is running IIS 5.0 to a Web farm that uses network load balancing


http://support.microsoft.com/kb/922733/en-us


4.
You may experience poor Web performance when you use Internet Explorer 6 to try to access a Web application that is hosted on Internet Information Services 6.0


http://support.microsoft.com/kb/922703/en-us


5.
High Performance Web Sites: Rule 13 – Configure ETags


http://developer.yahoo.net/blog/archives/2007/07/high_performanc_11.html


6.
面向站长和网站管 理员的Web缓存加速指南

http://www.mnot.net/cache_docs/#BROWSER
http://www.chedong.com/tech/cache_docs.html
中文翻译


7.
Http 1.1 Etag 与 Last-Modified


http://www.dbanotes.net/web/http_11_etag_lastmodified.html


8.
Hypertext Transfer Protocol -- HTTP/1.1

http://www.w3.org/Protocols/rfc2616/rfc2616.html

9.
The value in the ETAG field is updated when you modify a metabase property in IIS 6.0


http://support.microsoft.com/kb/900245/en-us


10.
Apache配置文档


http://doc.chinahtml.com/Manual/ApacheManual/mod/core.html


11.
浅谈Etag

http://bbs.chinaunix.net/thread-1186771-1-1.html

前端页面优化-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-Modified,ETag


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

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

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