阅读本文英文原文(翻译:刘松涛)
经Port80软件授权发表
本文的第一部分 (二月份)介绍了如何通过优化代码来尽可能少的传输数据,在本文的第二部分中,我们将着重介绍如何利用Web端的缓冲技术(caching)来尽可能降低传输的频繁度。一旦您开始注意进行有效的缓冲设置,您便可以极大地减少网页加载的次数,尤其对于经常访问您网站的常客和忠诚的访问者来说更是如此,而且还可以降低您整体带宽的消耗,并减少您有限的服务器资源的占用。
Web缓冲的种类
缓冲技术的原理很简单。为防止每次都重复地加载同一内容而带来的各种资源消耗和浪费,我们保留一份内容的本地副本,并且只要它还有效就可以反复使用它。最常见的网页缓冲是浏览器缓冲,浏览器缓冲在最终用户的本地硬盘上储存了图像和其他网页对象的副本,以供重复使用。除此之外,还有别的缓冲,比如web服务器上的,还有网络路径上的,甚至最终用户本地的网络上都有—不过,这些不同的缓冲其目的是基本一致的。从本地浏览器的缓冲上开始,往外有本地网络的代理(proxy)缓冲,这样如果代理缓冲中有相同的内容,本地网络中其它的用户便不必跑到远端的web服务器上去索取内容了,他们直接使用代理缓冲里这些相同的东西就可以了。再往外,你的ISP和再往外的各个ISP们可能都有一个类似的代理缓冲。最后,在web服务器上还会使用一个叫做‘reverse proxy cache’的缓冲来保持住最后生成的网页,准备提交,这样可以减轻服务器重复生成和提交被请求的内容的负担。
我们可以把上述各种网页缓存分成两类:私有的(private)和公共的(public)。私有缓冲,基本上就是浏览器缓冲,对于一个单个的用户代理(user agent)来说是独一无二的,也就是说只有拥有它的某个最终用户才可以使用。其他的缓冲,比如proxy和reverse proxy缓冲都是公共缓冲。它们是共享资源,其中存储的东西可以被不止一个最终用户使用。图一展示了网页主要常见的缓冲类型:
图一:网页中的缓冲
上述图示说明了我们讨论的一个关键问题:网页缓冲存在于网站和网页服务的各个地方,它们尽可能的保存您要访问的网站内容。一方面,从网站性能的角度来讲,我们要让缓冲能够自行发挥作用;另一方面对其能够进行有目的的控制也很关键,比如我们必须可以规定哪些对象可以放入缓冲中、哪些不可以,以及对象保留多长时间等。
新鲜程度和有效性
为了更好的使用缓冲,包括更有效的使用浏览器缓冲,我们需要对某一个资源的有效性提供指示,以表明这个资源是否还可以放在缓冲中。更具体的说,我们需要对网页对象提供一系列的缓冲规则,比如设置失效期,达到失效期的对象就不能再保存在缓冲中了。幸运的是,对于依据缓冲控制规则失效的东西,我们有一些工具来处理它们。
有两个概念控制着缓冲如何工作:新鲜程度(freshness)和验证(validation)。新鲜程度指的是某个放在缓冲的对象是否是新的,换成更技术化的术语来说,就是这个在缓冲中的对象的状态是否和其相对应的在web服务器上的资源的状态一致。如果浏览器缓冲或者其他的缓冲没有足够的信息表明其是新鲜的,则系统就会产生警告错误并把这个对象当作过期的或者是不新鲜的(stale)。验证则是某个缓冲检查原始服务器来确认是否有潜在不新鲜的对象的过程。如果服务器确认某个缓冲对象仍是新鲜的,则浏览器继续使用本地的资源,否则服务器就会送出一份新鲜的拷贝。
一个简单的缓冲例子
我们举一个例子来说新鲜和验证的概念。在此我们使用浏览器缓冲为例,别的公共缓冲的核心原理和浏览器缓冲是一样的。
第一步:远程站点有一个网页叫做page1.html。这个网页引用了image1.gif、image2.gif和image3.gif,并且有一个到page2.html的链接。当我们第一次访问这个网页的时候,HTML和相关的GIF图像就会被一个接一个的被下载到本地的浏览器缓冲中
图二:第一次缓冲加载
一旦数据被下载到缓冲中,这些数据就会被加上‘邮戳’,这个邮戳就标示了它们从哪里来、何时被访问过。邮戳中也有可能包含有第三条信息:何时需要被重新下载。但是,大多数的网站都不会给它们的数据加上这第三条信息,所以我们也假设我们的例子中也不涉及到这第三条信息。
第二步:用户点击了page2.html的链接,这个网页以往从未被访问过,这个网页引用了image1.gif、image3.gif和image4.gif。此时,浏览器下载了这个网页的标示代码,但问题来了:还需要重新下载image1.gif和image3.gif吗,既然它们已经存在于缓冲中了?这个问题的答案显然是不必重新下载。不过,我们如何保证这两个图像自打上次存到缓冲中后,从来没有修改过呢?如果没有缓冲控制信息,可能我们没法保证。所以,浏览器会给服务器发送请求来能够重新验证这些图像是否被修改过。如果没有被修改过,服务器就会快速返回一个304Not Modified
回复,告诉浏览器可以放心的使用缓冲中的图像。然而,如果图像被修改过,浏览器就需要下载这个图像的新鲜的拷贝了。这个常见的请求-回复的循环是这样的:
图三:检查缓冲
从这个简单的例子中可以很明显地看出来,,即使当CSS、图像、和JavaScript是新鲜的,我们也不一定能够享受到缓冲带来的好处,因为浏览器访问一个对象前都要先到服务器那里兜个圈子。
IE 浏览器中缺省的设置是‘自动’,这个设置可以在单次浏览器会话中跳过对缓冲对象再验证的过程,这在一定程度上减少了浏览器和服务器之间的持续的来回通信。您可能注意过在同一次的浏览会话中,一般来说重复访问同一个网页的加载速度都比较快。您如果在浏览器设置中选择‘每次访问网页’这个选项,则您就会发现性能上的下降,这是很多个304Not Modified
回复造成的。
图四:IE的缓冲控制对话框
注意:尽管IE的‘智能缓冲’可以有效地减少不必要的验证请求,它也是IE不断提醒用户若要看到新内容请清除缓冲的罪魁祸首。所以,使用缓冲,必须折中考虑很多事情。
设置缓冲控制策略
通过最小化来回通信的次数可以对浏览器加载次数产生巨大的变化。这种变化最显著的情况出现在下述情况下:经过了第一次访问后,用户再次光顾这个网页。这种情况下,所有的网页对象都得再验证,每次都会消耗宝贵的时间,更不用说消耗的带宽和服务器资源。另一方面,恰当的使用缓冲控制可以让浏览器直接从缓冲中加载原来加载过的对象,而不必‘长途跋涉’再次造访服务器。添加缓冲控制规则的效果,可以在网页的加载时间上看得出来,即使用户使用的是宽带,也可以感觉得到网页渲染的速度变快了。除了用户确实感觉得到的变化外,web服务器也可以从处理大量的缓冲再验证请求的负担重稍事解脱一些,这样进而可以更好的提供服务。
不过,为了更充分的享受到缓冲带来的好处,程序员需要花些时间精心制作一些缓冲控制策略,来按照网站对象可能的生命周期对其进行分类。这里有一个例子,说的是某简单的电子商务网站的一整套缓冲控制策略:
Object Type | Duration |
Navigational, logo images | One year |
CSS, JavaScript | Six months |
Main header images | Three months |
Monthly special offer image | One month |
Personalized special offer image (private) | Two weeks from first access |
All other content (HTML, ASP, etc.) | Do not cache |
在这个表中,从缓冲控制的角度来看,共有六类不同类型的对象。既然和公司标识及其他品牌标示有关的东西不太可能变化,这些导航和公司标识的图片就可以看作是基本上永久不变的。CSS和JavaScript文件一般来说也是至少半年才会变化。因为网站内容对于搜索引擎优化和用户体验来说至关重要,因此主要的页眉图像可以设置成变化稍微频繁一些。每月的‘特价商品’图像,当然它的保鲜期也就设置成一个月了。还有一些针对个人的特价商品,它们的图象保存在用户的缓冲中,其保鲜期设置成两周,也就是从首次访问开始算起的过期时限是两周。注意,这个类别被标注成‘私有’,以表示这种缓冲不能存在于共享或代理缓冲中。最后,除上述之外其他所有别的内容,它们的缺省都是不能作为缓冲内容的,依次来保证这些文本和动态的内容每次访问请求都是新鲜送出的。
对于缓冲HTML网页,你必须得小心谨慎,不管它们是不是静态生成的,除非你很清楚其中的玄机,如果你不清楚的话,就最好保证这些网页不要缓冲。如果某用户缓冲了你的HTML网页,你设置了一定长度的过期时间,则在过期之前,无论你的网页做了多少更新,该用户都不会看到这些变化。另一方面,如果你只是对于独立的对象进行缓冲的话,比如图像、Flash文件、JavaScript、和样式表单等,简单的进行重命名就可以替换掉这些缓冲内容。打个比方,您设定了一个策略,想要每年更新您站点的标识文件,但只过了半年的时候,您的公司要做品牌形象改变,并且需要对网站进行相应的修改。幸运的是,如果您没有设定HTML网页要做缓冲,您只消改变标识文件的名称(比如logo.gif变成newlogo.gif)并更改相应HTML文件中的 <img>
引用部分即可。当这个HTML文件被解析时,浏览器就会意识到在它的缓冲中并没有这个新的标示文件,浏览器就会去服务器上下载它。当然了,老的标示文件虽然仍存在于用户的缓冲中相当长的一段时间,但是再也不会被用到了。
在慎重的考虑过哪些对象可以或者不可以被存入缓冲中,下一步就是要部署这些策略。
管理缓冲
有三种方法来设置缓冲控制规则:
<meta>
标签来指定缓冲控制headers 上述三种方法各有其优缺点,我们简要的来总结一下。
通过<meta>
标签来实现简单的缓冲
最简单设定缓冲控制的方法就是使用<meta>
标签了。比如,我们可以通过设置Expire这个header来说明未来的什么时间过期:
<meta HTTP-EQUIV="Expires" content="Sun, 31 Oct 2004 23:59:00 GMT" />
在上述例子中,浏览器解析到这个HTML时,会认为这个网页到了2004年10月就会过期,并把这个规则加到浏览器的缓冲中。因为这个网页会通过 Expires
这个header被打上时间戳,所以浏览器在这个时间没有到来之前不会重新向服务器请求这个网页,而是直接从缓冲调出,或者除非用户修改了浏览器中缓冲的设置,或手动的清除了缓冲。
当然了,缓冲网页虽然可以带来好处,但如上面所述有时我们并不想缓冲它们,这时您可以把Expires
的值设置成过去的某个时间,比如:<meta HTTP-EQUIV="Expires" content="Sat, 13 Dec 2003 12:59:00 GMT" />
您可能会想用户电脑时钟可能会和服务器的不一样,您可能因为这个会把过期时间设置成很久很久以前,但在实际中,这基本上不是什么问题,因为缓冲控制参考的就是服务器的日期。
和HTTP1.0和HTTP1.1兼容的浏览器都可以使用带有以前时间的Expires
。除此之外,还有两个 <meta>
标签也很有用,它们用来保证某个网页没有被存在缓冲中。Pragma
标签被用来和HTTP1.0浏览器的缓冲控制中,而Cache-Control
标签则是针对HTTP1.1客户端的。无论何种浏览器,也不管浏览器的版本是多少,只要您是要保证某个网页不被存入缓冲中,这两个标签就很有用:
<meta HTTP-EQUIV="Pragma" content="no-cache" />
<meta HTTP-EQUIV="Cache-Control" content="no-cache" />
这两个标签很简单,它们有一个重大问题—中间的代理缓冲读不到它们,因为中间代理不会去解析HTML数据,所以就要依赖HTTP的header来进行缓冲控制了。因为如此,反正浏览器也要读HTTP headers,<meta>
标签的缓冲控制并不太被网站开发人员所青睐。
编程的缓冲控制
多数的服务器端的编程环境,如:PHP,ASP,和ColdFusion都可以让你修改HTTP Headers来实现一些特殊的功能。比如,在ASP中,你可以在网页的上面加上一些代码,来调用一些现成的Response对象属性:
<%
Response.Expires = "1440"
Response.CacheControl = "max-age=86400,private"
%>
在此你通过ASP来生成适合HTTP1.0的Expires header和适合HTTP1.1的Cache-Control
header。同时在此你还未这个缓冲对象指定了保鲜时间(freshness lifetime)为24小时(注意:Expires
值的单位是分钟,而Cache-Control
值是秒。)。所以在HTTP响应时,下列header就被加上了(这里我们假设‘现在’是二月十三日、星期五的8:46 PM,格林威治标准时。):
Expires: Sat, 14 Feb 2004 20:46:04 GMT
Cache-control: max-age=86400,private
这个机制或类似的其他主流服务器端编程环境下的机制比单单使用 <meta>
标签要可靠的多。所以,在使用服务器端编程和 <meta>
标签都可以的情况下,要尽可能选择使用服务器端编程来控制缓冲。
不过,服务器端编程环境也面临着一个无法解决的问题。假设,若生成的ASP文件中调用了几个图像,并且根据你设定的缓冲策略,它们的保鲜期是一年。那么,你将如何通过部署HTTP headers来告诉缓冲,这些图像要保存那么长时间呢?你可以使用服务器端的脚本来返回这些图像,但是这样既复杂又浪费资源。在服务器端本身设置缓冲控制信息,这是较理想的为静态的外部的代码(如CSS、JavaScript和二进制对象等)设置缓冲控制信息的方法。
编程缓冲控制
Apache和Microsoft IIS都提供一系列的缓冲控制机制。麻烦的是,这两种常用的web服务器使用不同的方法来缓冲,并且缓冲控制策略的授权也不完全掌握在对网站资源非常熟悉的开发者手中。
Apache上的缓冲控制
就设定缓冲策略而言,在Apache模块正常安装的情况下,Apache比IIS要容易一些。
Apache的服务器管理员,可以通过在服务器的配置文件中(一般来说是httpd.conf)设定mod_expires值来给不同的对象设定保鲜期。在Apache中,Virtual Host
和Directory
容器可以用来为不同站点指定不同的指示(directive),也可以为同一站点内不同的目录指定不同的指示。这一点,比起metabase脚本对象或图形界面的IIS来说要方便许多。
mod_expires的ExpiresByType
指示就更好用了,通过它只要一行代码,您就可以设定好某给定MIME类型的所有文件的保鲜期。这个指示可以让您轻松的给站点内的所有的脚本、样式表单、以及图像文件设定缓冲策略。当然,基于对象类型或目录,您还可以制定更为精细的策略。在此情况下,通过给某个特定目录及其子目录的某个.htaccess文件中使用指示(directive),在主配置文件中的设置就可以不考虑了(需要服务器管理员的审慎决定)。通过这种方法,开发人员就可以编写和维护自己的缓冲控制指示,而不需要拥有系统管理员的权限,即使在共享的主机托管环境下也是如此。
若您的Apache服务器起初没有mod_expires这一项,我们可以生成它,最好方法就是把它制作成一个共享对象(最简单的方法是使用aspx),之后再在httpd.conf文件中加上这样一行:
LoadModule expires_module modules/mod_expires.so
如上述,您可以把您配置的指示直接放在httpd.conf文件中。不过,不少管理员更倾向于在外部配置文件中来使用这些东西,这样可以让配置文件干净利落。在我们的例子中,我们也遵循这样的方法,在httpd.conf中使用Apache的Include
指示(其中的 IfModule
容器不必一定使用,它是可选的,使用它比较经典、也比较安全):
<IfModule mod_expires.c≶ Include conf/expires.conf
之后,我们可以在具体的模块配置文件(expires.conf)中来找到这些控制mod_expires行为的各种指示。下面是一些指示的例子:
ExpiresActive On
ExpiresDefault "access 1 month"
ExpiresByType image/png "access 3 months"
ExpiresActive
指示的作用是让mod_expires生效,ExpiresDefault
指示的作用是设定缺省的保鲜期,当有些文件中没有指定保鲜期时,则使用这个缺省的保鲜期。注意其中设定保鲜期的语法;时间单位用什么都可以,可以用秒、也可以用年,基本单位在modification
和access
中指定。 我们来看看刚才说过的非常有用的ExpiresByType
指示,我们在此的例子中要设定服务上所有的.png文件:
<Directory "/usr/local/apache/htdocs/static">
AllowOverride Indexes
ExpiresDefault "access 6 months"
</Directory>
最后,我们来看看Directory容器的使用。它将忽略目录/static
下所有的内容。这个目录拥有自己的 ExpiresDefault
指示,以及AllowOverride
指示,AllowOverride
指示的作用是允许为自己及其子目录进行设置,该设置将忽略.htaccess文件中的有关设置。.htaccess文件,则是这个样子的:
ExpiresByType text/html "access 1 week"
注意这将忽略所有的指示,否则这些指示会对/static
目录及其子目录中的所有带有text/hmtl的MIME类型文件起作用。通过使用配置文件中的指示组合,以及通过.htaccess来忽略这些指示,我们基本上可以制定出各种各样的缓冲控制策略,不管该策略有多么复杂,都可以通过管理员或得到合理授权的开发人员来生成。
IIS上的缓冲控制
如果您使用微软的Internet Information Service (IIS),设置缓冲控制规则时,您必须有权限使用IIS的Metabase,而IIS Metabase一般是通过Internet Service Manager (ISM)来管理的,ISM是微软管理控制台,通过ISM我们可以控制管理设置。
在IIS中设定失效时间,操作比较简单,先运行ISM,之后找到那个你要设定失效时间的目录或文件的属性菜单,之后点击‘HTTP Headers’标签。接下来,选中’Enable Content Expiraction’的复选框;然后,再点击单选钮进行下一步的选择。您可以选择让某个内容立即失效,或设定相对失效期(单位:分、时、或天),或绝对失效期。注意在此之后,Expires
和Cache-Control
就会被加上了。基本上是下面的样子:
图五:IIS中缓冲控制
尽管它的GUI相对友好,但是给IIS上不同类型文件设定不同的缓冲控制策略却显得比较笨拙。如果IIS上站点的文件不按照保鲜期来组织的话,那就更麻烦了。但如果您恰巧是心中牢记缓冲策略来设计网站的话,并且你把不同类型的文件放在不同的目录下,比如 /images/dynamic
, /images/static
, /images/navigation
等,那么通过MMC设置缓冲策略就容易的多了。不过,若您不是这样,或是您正打算优化现有的网站,那您基本上就不得不一个一个文件、一个一个目录的来设置策略了,这绝对是超级麻烦。
比起Apache更麻烦的是,在IIS中给开发人员授权让他们来设定缓冲策略,也没什么好的方法,因为修改相应的设定需要访问MMC。不过还好,有第三方软件工具—Port80软件公司提供的CacheRight 可以进行较为方便的设定。
和mod_expires的工作原理类似,CacheRight软件创立一个简单的、基于文本的规则文件,它存在于每个网站的文件根(document root),通过它管理员和开发员可以来给整个网站设定失效指示。而且,CacheRight在ExpiresByType指示之外,还添加了ExpiresByPath
指示,使前者更完善,这一点超越了mod_expires。有了这一功能,在IIS上为不同文件类型设定策略、或者让一些指定的文件或子文件集忽略某些策略,就相当的容易了。我们看看下面的例子:
ExpiresByType image/* : 6 months after access public
ExpiresByPath /navimgs/*, /logos/* : 1 year after modification public
这里,所有的图像文件的保鲜期都是六个月,只有在navimgs
和logos
目录下的文件除外。和mod_expires一样,CacheRight可以帮助您设定相对于文件修改时间的失效时间,也可以设定成相对于用户第一次访问的时间。这样的灵活性对于发布或更新的时间安排可能有变化的情况非常有用,而发布和更新计划常常变化是非常普遍的。
无论您使用什么样的web服务器,您都值得花些时间来了解如何在服务器端来管理缓冲控制。在编程的缓冲控制中,有规律的中间缓冲和浏览器缓冲都可以很好的运用指示(directive);而在 <meta>
标签的缓冲控制中,只有浏览器缓冲可以很好发挥作用。而服务器端的缓冲控制的使用,比起编程或<meta>
标签来则要容易得多,尤其是应对包含大尺寸的对象(比如:图形和Flash等)的网站更是好处多多。因为,正是对图像和Flash这些相对比较占流量的对象来缓冲,才能最有效提升整体性能。
缓冲的好处
至此为止,对于复杂的缓冲问题,我们也只是简单介绍了一些皮毛,希望我们能够给提升网站性能的一个潜在道路指明方向。我们特别希望您通过本文,能了解为什么制定一套综合性的网站缓冲控制策略很重要,我们也希望您现在已经掌握了一些有效地实施这些策略的方法。正确合理的运用这些方法,带来的效果是显著的—能够极大地加快网页加载的速度,尤其对于您网站的老顾客更是如此。此外,缓冲的使用还可以更有效的利用网络带宽,并能减轻服务器的负载。只要您稍稍有意识的修改一下HTTP headers就可以取得上述效果,或者用最少的软件投资即可制订好有效的、基于失效时间的缓冲控制—最省钱省力实现网站性能优化的方法之一。
Thomas A. Powell 是PINT公司的创始人,也是加州大学San Diego分校计算机科学系的讲师,以及一些网页开发书籍的作者,其所著书目包括《HTML & XHTML: The Complete Reference》和 《JavaScript: The Complete Reference》等。
Joe Lima 是Port80软件公司的首席构架师(architect),同时教授UCSD 扩展的服务器技术。
Port80软件公司
Port80 Software, Inc. 是微软 Internet Information Services (IIS) 网络服务的领先的开发商. 公司同时提供w3compiler, 一套优化代码的桌面应用软件。Port80 Software 是微软认证合作商(MCP ISV)。它位于San Diego, CA. 更多信息请见公司网站 www.port80software.com.
从Amazon.co.uk购买作者的书籍:
- HTML and XHTML: The Complete Reference…
- JavaScript: The Complete Reference