文章存档 » 六月 2011

读书分享:《高性能网站建设指南》

high-performance-web-sites

同样的网络环境,看着别人的网站“唰”的一下就展现出来,你是否和我一样,心急如焚,盼望着早一点攒出一笔钱,给服务器加点内存?或者你已经挽起袖子,开始研究数据库优化?又或者你在暗自思量着可以把哪些设计模式或编码技巧运用在自己的后台代码里,盼望以此带来性能上的巨幅提升? 哦,别激动,很多时候事情并没有你想象的这么严重。

这是High Performance Web Sites(以下称HPWS)的译者在序中说到的前两段。对于网站的响应速度的优化,后台的优化和硬件的升级往往伴随着很多困难,诸如成本,工作量等。但实际上前台的优化更为重要也更为高效,为什么呢?HPWS中引入了一条性能黄金法则:

只有10%~20%的最终用户响应时间花在了下载HTML上,其余的80%~90%时间花在了下载页面中的所有组件上。

这就是为什么我们要把精力集中到对于前台的优化上。

HPWS中引入了14条可以实施的方案用于优化前台性能,我将逐条说明。

减少HTTP请求

减少HTTP请求的有效方法,HPWS中提供了以下几个方法:

图片地图

对于一个和图片关联在一起的超链接,使用图片地图能够有效减少请求数,因为这样在不改变显示效果的前提下,将很多张图片改为了一张。

CSS Sprites

这种方法类似于图片地图,也是用于合并图片,而且更为灵活

内联图片

这种方法是将小块的图片内联为“立即数”,图片的全部数据就在其URL自身之中

合并脚本和样式表

虽然软件工程师会推荐将代码按照模块化原则分开放在多个小文件中,但这样做会降低性能,因为每个文件都会导致一个额外的HTTP请求,所以合并脚本和样式表也可带来性能的提升。

使用内容发布网络

需要这样做的原因是,当你的用户在地理上分布很广时,你就有必要在多个地理位置不同的服务器上部署内容。所以使用内容发布网络(CDN)能够使HTTP请求响应的时间缩短。CDN是一组分布在多个不同地理位置的Web服务器,用于更有效地向用户发布内容。

添加Expires头

Expiers头用于控制浏览器对于缓存的保存时间。因此这一条规则的含义是为你的页面添加长久的Expires头,这样可以有效地将大部分组件保存在缓存中,这样当用户再次访问页面时,浏览器将使用缓存的图片从而减少了HTTP请求的数量,从而节省了时间。

Expires头在服务器端配置,如Apache中有mod_expires模块。使用与不使用Expires头的HTTP响应将如下所示

不使用Expires头

使用Expires头

两者在YSlow中对Expires的评价中分别得到了F和A(注:YSlow是FireFox下的一款插件,用于对一个网站速度进行多个方面的评价)。可以看到合适的Expires头对于网站速度的重要性。

压缩组件

减少HTTP响应的大小来减少响应时间这一点很容易理解,只传输很少的数据,肯定相应有很少的响应时间。减小响应包大小的方法之一是使用gzip编码来压缩HTTP响应包。当然也有一些其他的方法,但都不如压缩组件来得效果好。

压缩组件的配置同样在服务器端,如Apache的modgzip及moddeflate

组件压缩后将在响应头中有显示:

对于压缩的内容,压缩所有内容在YSlow中得分最高A,不进行压缩将得到D,只压缩HTML将得到C

将样式表放在顶部

CSS样式表对于用户而言并不是最重要的,因此若将CSS样式表放在顶部将会延迟页面中其他重要组件的下载,但实际情况与此相反(请到文章末尾给出的链接中体会)将CSS放在顶部的加载速度快于将CSS放在底部。这是因为放在底部的CSS将会阻塞浏览器对于页面的渲染,因为假如没有得到准确的CSS信息,那么对于页面的呈现就显得是一种浪费,所以为了避免这点,浏览器会等到CSS下载完毕后再呈现页面,这样就导致用户界面很长时间会出现“白屏”的现象。显然这种用户体验是不好的,因此将CSS放在底部,将有助于浏览器逐步呈现页面,而不是给用户一个浏览器假死的现象。

将CSS放在底部

将CSS放在顶部

两种做法在YSlow中分别得到B和A

将脚本放在底部

与CSS样式表类似,脚本也会阻碍页面的逐步呈现,但解决的方法与CSS样式表正相反:将脚本放在底部将加快浏览器的呈现速度。这涉及到浏览器的行为。浏览器在下载组件时,往往采取并行下载多个组件的策略,并行下载数与浏览器相关,但脚本将阻塞并行下载,其中一个原因是,脚本可能使用document.write来修改页面内容,因此浏览器会等待,以确保页面能够恰当地布局,而另一个原因是为了保证脚本能够按照正确的顺序执行。如果并行下载多个脚本,就无法保证响应是按照特定顺序到达浏览器的,这使得脚本的执行顺序不可预知,假如脚本之间有关联,就有可能产生错误。文章后面的链接中有一个例子,从里面可以很明显地看到js脚本是如何阻塞呈现的。

但是对于阻塞并行下载,不同浏览器的行为并不相同,经我们的观察和讨论得出,有些浏览器阻塞的并非是并行下载,而是页面的渲染,但不论如何,这都影响了页面的呈现,所以将脚本放在页面的底部能够加速页面的呈现。

在firefox中js脚本和其他组件也是并行下载的(使用firebug查看)

避免CSS表达式

CSS属性设置中可以接受js表达式,在某些情况下(譬如动态改变样式)非常有用,例如在IE中不支持min-width属性,这样就需要使用CSS表达式来设置使得页面拥有最小宽度。但是浏览器对于CSS表达式的频繁求值会导致CSS表达式的低下性能。在文章末尾提供的链接中的例子可以看到,CSS表达式的求值比想象中要频繁。

需要使用CSS表达式又怕降低性能,解决的方法有两个:一个是使用一次性表达式,在一次执行中重写它自身,这样可以将属性设定为一个确定的值,而避免了表达式的重复求值。

一次性表达式

另一种方法是使用事件处理器,这样已经脱离了CSS表达式的范畴,但使用这种方法也能达到同样的效果,而且不需要频繁求值,即将对表达式的求值与一个事件关联。

事件处理器

将事件与函数相关联

使用外部JavaScript和CSS

使用内联还是外部的js和CSS是一个很基本的问题,在HPWS中的说法是“纯粹而言,使用内联js和CSS更快一些”但是我个人实际的实验却表示使用外部的js和CSS更快,我想着其中很大一部分原因是并行下载,因为内联或者外部,组件的总大小是固定的,而内联将使得并行下载不能,所以反而会降低速度,虽然这样减少了HTTP请求。但实际上当然还是使用外部js和CSS较好,因为这涉及到缓存的问题,外部js和CSS可以保存在缓存中,这样用户下次就不必下载了,内联的文件则不然。

在内联比外部快的前提下,HPWS提出了两全其美的方法:加载后下载,即使用户下载内联文件,但是在页面中加入脚本,使得在用户加载页面结束后开始下载外部的js和CSS,同时还可以再服务器端添加动态内联,判断用户是否有当前组件的缓存,这可以通过cookie实现。

下载后加载的代码:

精简JavaScript

减小HTTP相应包的方法,除了压缩组件之外,精简js也是一个有效的方法,精简的含义是从代码中移除不必要的字符以减小其大小,进而改善加载时间的实践。在代码被精简后,所有的注释以及不必要的空白字符都将被移除。这样可以减小需要下载的js脚本。混淆是可以应用在源代码上的另外一种方式。和精简一样,它也会移除注释和空白,同时它还会改写代码。作为改写的一部分,函数和变量的名字将被转换为更短的字符串,这是的代码更加精炼,也更难阅读。但混淆有可能引入错误,混淆会改变js符号,这些都是混淆带来的风险,所以精简是最常用的方式。

精简后的脚本

混淆后的脚本

并且不要忘记精简CSS脚本

移除重复脚本

重复脚本经常发生,因为不同的团队都会向项目贡献代码,但是他们相互之间可能不知道对方已经包含了自己想要的脚本,这样重复包含就发生了,重复的脚本会增加不必要的HTTP请求,从而损伤性能。解决的方法可以在后台添加一个脚本管理的模块,将已经包含的脚本假如一个列表,每次添加前加以检测,对于已经添加的脚本则不再添加。

配置ETag

ETag的作用于Expires头类似,都是标识了一个组件的缓存时间。不同的是Expires头是通过设置保存时间来设置缓存,而ETag是对一个特定版本的组件产生一个特定字符串,然后在以后的请求中通过比对此字符串来决定是否更新此组件。那么为什么要配置ETag呢,因为ETag通常由组件的某些属性来构造,这些属性相对于特定的、寄宿了网站的服务器来说是唯一的。当浏览器从一台服务器上获取了原始组件之后,又向另外一台不同的服务器发起条件GET请求时,ETag是不会匹配的。因此需要配置ETag的构造方法,使得ETag在不同的服务器上相一致。

ETag举例

使Ajax可缓存

由于Ajax是几种已有技术的组合,因而其优化方法大体可以依照前面的优化方法,如配置缓存,压缩组件,精简脚本,等等。

这就是前台优化的一些策略,每一个都很细节,但就是这些细节决定了页面的呈现速度。在阅读HPWS的过程中,我也不止一次感慨:只有对一种技术了解到细节层面才能称为是专家,大面上的东西很容易就了解了,但关键是能不能深入原理和细节。

相关链接

HPWS一书中各条规则实例

使用Gravatar – 普通用户&开发者

Gravatar是什么

Gravatar是www.gravatar.com 推出的一项服务,意为“全球通用头像”。如果在gravatar上注册了账号并在gravatar服务器上放置了头像,那么当在支持gravatar的blog或留言本上发言时,只要提供email地址,就能够显示与email相关联的头像。这样就为大家提供了一个统一管理多个头像的平台,只要站点支持gravatar,就不必特意为每个站点单独上传头像,省去了麻烦。

普通用户

作为一名普通的用户,如何使用Gravatar呢?

首先到gravatar上注册账号,很简单,只要填写一个常用的email地址,填写好密码,然后到邮箱里确认,就注册好了一个gravatar账号。随后用账号登陆gravatar,就可以开始为账号添加头像了,每个email可以和一个头像关联,上传头像的方式有很多种。

选择一种,并选好准备制作成头像的图片。随后进入编辑界面。

简单编辑后,就截取好了gravatar头像,接下来为头像选择等级,不同的等级会决定你的头像是否在站点中显示,假如你的头像过于限制级,则在不支持此等级的站点中不会显示,而只会显示一个缺省头像。

在这几个步骤之后,就添加好了gravatar头像,接下来进入管理界面。

在此界面下,可以对自己的多个头像和相关联的多个email进行管理。

以上是作为普通用户的gravatar使用方法,对于开发者,gravatar同样提供了一些便利的使用方式来丰富你的站点。

开发者

gravatar不但为普通用户提供了头像解决方案,还为开发者们提供了一些接口,方便开发者调用gravatar头像以及在用户gravatar头像中包含的简单profile。在gravatar首页中可以找到开发者文档的入口,里面有关于如何使用gravatar接口的文档。

最常用的调用方式是通过URL调用头像和用户profile。通过URL调用要经过以下几个步骤Creating the Hash将欲调用的用户的email字符串去掉空格,并将所有字母转换为小写,最后将其转换为md5码,以php语言为例:

接下来便可利用得到的字符串构造URL来调用gravatar中的头像了。简单调用,将得到的hash后的字符加在http://www.gravatar.com/avatar/之后,再将其写到<img>标签的src属性中,便可以调用了。如hash后得到的字符串为205e460b479e2e5b48aec07710c08d51,便可按以上方法调用为:

文件格式:如果需要文件的格式,也可以在后面加上后缀,如’.jpg’。在调用的时候,也可以进行一些简单的参数设置,如大小,缺省等。

图像大小:在URL后添加 ?s=(或?size=) 一个整数,便会返回一个指定大小(长宽一样)的图像。

缺省头像:当由于头像分级或未设置头像而无法显示时,可以设置一个缺省头像.设置缺省头像使用的是 ?d= 参数,既可以使用gravatar提供的几种参数,也可以链接到其他URL指向的图片,但要经过urlencode()函数的编码。如:echo urlencode( ‘http://example.com/images/avatar.jpg’ )这样便可将缺省头像指向一个URL。

还有一些其他的参数设置,如强制缺省,制定分级,详细内容请参考官方文档。注册gravatar时还会填写一些个人信息(邮箱,个人主页等)这些信息也可以通过gravatar URL来调用。方法也是先将email hash一下,然后加在http://www.gravatar.com/之后,注意,和调用头像不同,向这一URL发送请求,返回信息就包含着profile中的信息。

这样就得到了全部的profile信息,profile中包含若干键值对,其中键为信息的标题(姓名,电话等),值为信息的内容。 除此之外,gravatar还有远程调用的接口,用于在用户认证的基础上,修改用户gravatar账户中的信息,由于并不常用,所以不再赘述,有兴趣可以参考官方文档。

总之,gravatar的出现对于普通用户来说,提供了一个管理头像平台,能够为用户节省精力,对于开发者来说,在自己的站点中引入gravatar也能够为自己的网站增色不少。

参考资料