翻译:High Performance Web Sites(5)-Chapter 3

《High Performance Web Sites》 :Chapter 3

法则3:增加Expires Header  Add an Expires Header 

在您设计网页时,快速的响应时间不应该是你唯一要考虑的,如果仅仅是这样,那我们采用法则1,把我们的页面设计成一个极端的网页:没有任何图片,script,样式表。我们都明白,图片、script、样式表这些组件可以增强用户体验,虽然它们会给页面带来较长的载入时间。你幸运了,在这一章介绍的法则3,我就要向你介绍如何最大限度地利用浏览器的缓存来使这些页面组件更高效的为我们的页面服务。

现在的网页所包含的组件越来越丰富,java applet去了,Flash来了,script也不可缺少,随着div的大潮,css样式表也少不了…… 所以用户若第一次访问您的网页,则可能会产生多个HTTP请求;但如果您在请求返回中使用Expires头信息,则可以缓存这些组件,避免在随后的访问中产生不必要的HTTP请求,Expires头信息最常用于图片,其实它适合应用在所有的组件中,如scripts,样式表和Flash。但大多数当今的热门网站目前并没有这样做。在这一章节中我会提及如何让这些网站可以更快。在使用Header头信息时,也可能会有一定的额外开销,这点会在 “改变文件名”段落中再详谈.

Expires Header

浏览器使用缓存来减少HTTP请求数和减少HTTP的响应数据量,以达到更快的加载页面。web服务器通过Expries header来告诉web客户端(John:主要是浏览器)当前返回的组件在我指定的时间以前都是可用的,你(浏览器)可以留着下次备用(John:有点像我们在超市买牛奶时,随包装袋上返回的过期日期)。Expries header 在HTTP规范中描述为 “the date/time after which the response is considered stale.”,它附带在HTTP的响应中返回给客户端。

如上图,这是一个典型的expries header ,它告诉浏览器,直到2010年4月15日这个请示的返回都不会变化。如果这个头信息是随着一个图片返回的,那浏览器就会缓存这个图片,以为其后的页面浏览直接使用,以减少一个HTTP请示。 

Max-Age and mod_expires

在我解释缓存是如何更好的提升性能之前,有必要介绍一下与Expries header相关的几个头信息。Cache-Control header是在HTTP/1.1中被引入以弥补Expries header的不足。因为Expries header使用的是一个具体的日期,它就要求服务器与客户端之间要有严格的时钟检查,并且当过期日期到来之后,服务器端还必须配置一个新日期。

相反,Cache-Control 使用max-age来指定一个时间段来标识一个组件能使用多长时间。这个max-age是以秒为单位来描述的(John:有点保质期的概念)。如果在这个保质期内,这个组件再被请求,浏览器就直接使用缓存里的就行了。下面就是一个返回10年保质期的头信息。

使用Cache-Control虽然弥补了Expires的不足,但我们仍然还是得使用 Expires header以兼容那些不支持HTTP/1.1的浏览器(尽管这些流量可能还不到1%)。所以我们一般同时使用Expires和Cache-Control max-age,按HTTP规范这时会以max-age的时间为优先考虑,所以我们还是避免不了服务器与客户端之间的时钟检查和服务器端的新日期配置。幸运的是我们有Apache的 mod_expires模块,能让我们像设置max-age那样来配置Expires header,它使用ExpiresDefault来设置一个时间段,如下,为图片,scripts和样式表设置10年的有效期: 

<FilesMatch “\.(gif|jpg|js|css)$”>
ExpiresDefault “access plus 10 years”
</FilesMatch>
这个时间可以是年,月,周,天,小时,分钟甚至是秒。使用了这个模块,所有的响应返回中就会同时带着 Expires header 和 Cache-Control max-age 头信息:
其中Expires header所携带的过期时间具体是多少,则依赖于每个客户端什么时间发出的请求,在上面的例子中,始终是10年。这样就避免了我们每次在到期后都要重新设置服务器,mod_expires会搞定一切。图3-1是对10大网站使用这些headr的一个调查。有7家使用了这些header,其中有5家同时使用了
Expires 和 Cache-Control max-age(John:推荐)。各有2家分别只使用 Expires 或Cache-Control max-age。很遗憾有3家什么也没使用。 

 

Empty Cache vs. Primed Cache

使用Expires  header只会影响已经访问过的页面和组件,当用户第一次访问您的页面时无法避免的会有比较多的HTTP请求数,因为这时浏览器的缓存里是空的。所以如果您的网站能吸引用户访问更多的页面, 并经常在此停留,我们所做的缓存工作才有效果。

不仅仅是图片 More Than Just Images

一般Expires header常用于图片,其实这并不是最佳实践。Expires header应该应用于所有那些不经常变动的组件,script,样式表或Flash组件。

在理想情况下,所有组件都在一个页面内,并且都有一个未过期的Expries header,而随后浏览的页面只一个对HTML源文件的HTTP请求,其余的组件都在浏览器的缓存中直接提供,这时的响应时间会提高50%甚至更多。

我调查了美国的10大网站,并记录了其中有多少图片,script和样式表是设置了至少30天有效期的Expires 或是 Cache-Control max-age header,如表3-2所示,情况并不是太好,图中所列的是被缓存至少30天的组件数,分母是该网站上的所有的该类型组件数,分子是被加了过期header的组件数。
• 有5家网站把大多数图片的缓存有效期设为30天以上.
• 有4家网站把大多数样式表的缓存有效期设为30天以上.
• 有2家网站把大多数script的缓存有效期设为30天以上.

从上图的百分比中俺发现有74.7%的组件没有被缓存或是有效期低于30天。一种解释就是这些组件因为业务关系而不应该被缓存,如新闻网站CNN.com就是个例子,138张图片中没有一张被缓存,这是因为很多新闻照片要被不断更新而不必缓存在用户端,所以这时更适合用Last-Modified heade,来通过组件的最后修改时间判断是否要读取新数据。 

表3-2的最后一项,显示了未被缓存的组件的最后修改时间的中分线,就CNN.com来说有一半的未缓存组件的最后修改时间是在227天前了,很显然这些组件是应该被缓存的。

这种情况也曾发生在Yahoo!,在过去,Yahoo!并没有缓存script,样式表和一些图片,是因为我们认为这样是经常变化的,用户有必要每次访问时都去请求一次以获取最新的。可问题是,在实际应用中,这些组件并不是我们想像中的那样要经常变化更新,我意识到应该把它们缓存来提升用户体验。后来,Yahoo!选择了去缓存它们,哪怕会带来一些额外的开发花销,这就是我马上要谈到的Revving Filenames。

改变文件名 Revving Filenames

如果我们已经为组件设置了缓存,可当这些组件又需要更新时,用户如何才到得到最新的组件呢?由我们前面介绍的Expires 或是 Cache-Control max-age header 都会让浏览器不检查任何改变,直接从本地硬盘中读取缓存,除非是过了有效期。所以我们能做的,就是在HTML页面中改变这个组件的文件名。在Mark Nottingham的文章”Caching Tutorial for Web Authors and Webmasters“谈到这个问题:

最优的方案还是改变它们的链接;只有这样,用户才能从服务器上获取更新。(The most effective solution is to change any links to them; that way, completely new representations will be loaded fresh from the origin server.)

说白了,还是要改变文件名。在已有的页面中改变文件名可能是件头疼的烦事,但是,不得不这样,如果您使用的是PHP,Perl等动态的页面,不妨利用一个变量为所有的组件命名,这样换起文件名来比较省事。在Yahoo!,我们是在组件名后面跟上版本号(如,yahoo_2.0.6.js),把这个版本号做为一个变量写在程序中,真的很省事。


举例 Examples

下面的两个链接,包包含有相同的组件:6个图片,3个script和1个样式表。第一个链接没有任何Expires header,而第二个链接,全有。
No Expires
http://stevesouders.com/hpws/expiresoff.php
Far Future Expires
http://stevesouders.com/hpws/expireson.php
我在900Kbps的DSL下测试,加了过期头设置的链接页面访问时间会减少600~260毫秒,能提升57%,如果页面的组件再多一点,这个百分比还会更高。

要说明一点,并不是说没有加过期头的组件就不会被浏览器缓存,它们也会被缓存,还记得我在Chapter B中介绍的有条件的GET请求吗,只不过,会多一个HTTP请求到服务器端以询问这个组件是否有效,如果这个组件的最后修改时间和以前一下,则直接使用缓存中的版本,HTTP返回中将不带这个组件的具体数据了。

为您页面上的组件贴上保质期吧,再加有有条件的GET请求,带来的会是减少近一半的HTTP请求数还有不必要的数据传输。

 

添加评论