web应用程序的性能研究
前言
查找Best Practices for Speeding Up Your Web Site时,找到的。英文首篇地址: http://web-performance-research.blogspot.com/2008/05/performance-research-part-1-what-8020.html。中文译文首篇: http://www.cnblogs.com/sanjia/archive/2010/12/23/performance_research_part_1.html
关于减少HTTP请求,80-20法则告诉我们什么
毫无疑问,用户喜欢快的站点,我工作在雅虎一个致力于测量和提高性能的团队,我们工作的一部分是做一些和网页性能有关的实验,我们将分享我们所发现的,这样将会有更多的前端工程师加入我们从而提高用户体验。
80/20性能法则
Vilfredo Pareto,是一个90年代前期的经济学家,他发现了一个著名的规律:一个国家80%的财富被这个国家20%的人所掌握。这个后来被概括为大家常说的帕累托法则(也被叫做80-20法则),这个法则适用与各种现象,80%的结果都是由20%的原因导致的,我们在软件工程中同样可以看到80-20法则:我们把80%的时间花在了20%的代码上,当我们优化我们程序的时候,我应该知道集中精力在20%的代码,当我们优化网页的时候,这个规律同样适用,网页性能优化的一部分是组织HTML,但是这部分只占了用户响应时间的20%,我们更应该关注另外占去80%时间的部分。
使用网络监视器,我们可以发现到底是什么占去了80%的时间,图1显示了当我们在没有浏览器缓存的情况下打开http://www.yahoo.com各个部分占用时间的情况,图中每一条代表了一个特殊的组件,并且以浏览器开始请求的时间为顺序排序。图中第一条代表的是浏览器请求HTML所花的时间,看图可以知道,请求HTML仅仅占用了整个响应时间的10%(服务器组织HTML语言,并且返回到浏览器)。余下90%的时间用于获取其他组件,包括图片,脚本,样式等等。
图1(打开http://www.yahoo.com)
图2显示一些主流的站点下载HTML语言使用的时间是整个响应时间的5%–38%之间,其他的62%–95%的时间用于发送HTTP请求去获取页面上的所有组件(例如,图片,脚本,样式)。页面中组件的数量对响应速度的影响是很大的,因为针对不同的浏览器,它们一次只能并行下载2到4个组件。我们的经验表明减少HTTP的请求次数可以很大程度的降低浏览器响应时间,并且减少HTTP请求也是提高性能最容易的方法。
图2
不应该所有东西都保存在浏览器缓存中吧?
结论:减少HTTP的请求次数可以很大程度的降低浏览器响应时间,并且也是提高性能最容易的方法。
浏览器缓存的使用情况
在前一节中,讲述了关于减少HTTP请求,80-20法则告诉我们什么,浏览器花费了80%的时间去获取包括脚本、样式和图片在内的一些组件,减少HTTP请求的次数对于减少响应时间有着最大的影响,但是不应该所有的东西都保存在缓存中吧?
为什么缓存重要
区分空缓存的用户体验和全缓存的用户体验是很重要的。“空缓存”意味着浏览器绕过本地缓存而请求所有组件。“全缓存”意思是页面所有(至少大部分)组件已经在缓存中了,没有必要再发送HTTP请求去获取了(相应的HTTP请求避免了)。
“空缓存”的主要原因是用户第一次访问站点,浏览器必须要下载页面的组件,其他原因有:
- 用户以前虽然访问过该站点,但是已经清除了浏览器缓存
- 用户将浏览器设置为自动清除缓存
- 用户在刷新页面的时候使用特定的方法使得浏览器跳过了缓存,比如当你在刷新的时候同时按下Ctr键的时候,浏览器将跳过缓存,发送HTTP请求从服务器下载组件。
合并脚本,样式,图片等一些策略将会减少HTTP请求的次数(对空缓存和全缓存都有用),在一些组件的头文件中设置过期时间也会减少HTTP请求的次数(这个只对全缓存状态起作用)。
前面我们观察了当用户在空缓存状态下访问http://www/yahoo.com所花费的时间,当用户访问页面市,浏览器下载了将近30个组件(图3),图4显示了在全缓存状态下用户访问http://www/yahoo.com所花时间的情况,图中每一条代表了浏览器请求的一个特定组件,在全缓存状态下,大部分组件已经在缓存中了,所以浏览器只需要下载包括HTML在内的3个组件。
图1 在空缓存的状态下打开雅虎主页
图2 在全缓存的状态下打开雅虎主页
图5显示了一个关于当访问Http://www/yahoo.com时请求每个组件的大小和数量的汇总,用户将从全缓存中得到多少好处?在我家里面访问Http://www/yahoo.com,空缓存状态下需要2.4秒,而全缓存状态下只需要0.9秒,全缓存比空缓存减少了90%的HTTP请求、83%的数据流量。
图5
多少用户是在全缓存的状态下访问Yahoo!的呢?
雅虎的性能团队做了一个关于有多少比例的用户和页面在空调缓存状态下访问雅虎页面的实验。我们实验是通过分析用户对于一个组件(一个新的图片)的缓存情况来展开的。对于这个新的图片我们做了以下的统计:
- 有多少比例的用户在访问这个图片?
- 有多少比例的页面在访问这个图片?
这个新的图片HTTP头信息被设置为:
Expires(过期时间): Thu, 15 Apr 2004 20:00:00 GMT
Last-Modified(上次修改时间): Wed, 28 Sep 2006 23:49:57 GMT
当浏览器保存一个页面组件的时间,同时也保存了这个组件的过期时间和上次修改时间(HTTP头)。设置过期时间会强迫浏览器在访问页面的时候去请求图片,如果图片已经在缓存中,并且正在被重新请求,浏览器会把最后修改时间加入在HTTP头中,这就是传统的GET请求,如果图片没有被修改,服务器会返回一个304代码,所以对于浏览器的请求服务器会返回下面的两种代码:
200--浏览器没有缓存。
304--浏览器已经缓存了图片,但是需要验证最后修改时间
因为浏览器请求的返回状态是可以被记录的,所以我们是可以通过分析日志来检测空缓存和全缓存。
图6显示了用户和页面在空缓存的状态的下访问所占的比例,在实验的第一天,没有用户有这个图片的缓存,所以空缓存占据100%,随着时间的推移,更多的用户拥有了这个图片的缓存,所以空缓存的比例开始下降一直到一个平稳的状态。
图6
奇怪的结果
40%–60%的Yahoo!用户是空缓存的,20%的页面访问是在空缓存的情况下进行,据我所知,目前还没有其他的研究来说明这个事情,我不知道你是怎么认为的,但是我们的实验结果给了我一个大的惊奇。尽管你最大限度的使用了缓存,但是还是有很大数量的用户是在空缓存状态下的,这个又回到前一篇文章的话题“减少HTTP请求对于减少响应时间有最大的影响”,对于不同的网页,没有缓存的用户的比例是不同的,尤其对于那些有着大量活跃用户的页面。在我的学习中发现,不管如何使用缓存,至少有20%的时间页面是空缓存的状态下被访问的。
结论:始终要考虑空缓存的用户体验,空缓存要比你想象的普遍很多。
Cookies对于浏览器响应时间的影响
Cookies的应用实在是太广泛了,比如做身份验证的,做个性化的等,cookie中的信息在web服务器和浏览器之间通过HTTP头中进行交互。本文将讨论cookies对于整个用户响应时间的影响。
HTTP快速回顾
当浏览器请求一个页面的时候,web服务器会产生cookies,下面是一个当用户请求www.yahoo.com后,服务器发回来的http头的例子:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: C=abcde; path=/; domain=.yahoo.com
这个HTTP头包含这个响应的协议版本,状态代码,content-type(实体),Set-Cookie头也包含在响应中,在本例中cookie的名字是”C“,对应的值为”abcde”。注:ie6.0能允许的的最大容量是5051bytes,FF1.5的cookie最大容量为4096bytes。
浏览器会把cookie”C“存储在用户本地机器上面,在下一次请求的时候会发回到服务器,在本例中”domain=.yahoo.com “,指定了只有在.yahoo.com或者子域中的HTTP请求才能包含这个cookie,例如,如果用户访问finance.yahoo.com,浏览器将会在请求中包含”C“这个cookie,因为这个例子中没有包含过期时间,所以cookie的过期时间就是当前会话的时间(浏览器关闭后,cookie自动失效)。
下面是浏览器请求finance.yahoo.com时HTTP头的信息:
GET / HTTP/1.1 Host: finance.yahoo.com
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; ... Cookie: C=abcde;
可以看到在
Cookies对响应时间的影响
Yahoo!的性能团队做了一个实验去检测一个页面中cookie的大小,这个实验是在一个空页面的基础上做的(页面上没有任何元素),在这个实验中,主要的变量是cookie的大小,我们的实验主要测量不同的cookies大小下下载一个页面需要的时间,结果如图1所示:
图7
这个结果进一步说明我们应该尽最大的可能减少cookie的大小从而较少对于用户响应时间的影响。一个3000byte的cookie或者所有cookie的容量为3000bytes将会增加80ms的延迟时间(DSL访问),当用户使用拨号上网时这个延迟将会更长。
.yahoo.com的cookie有多大
在.yahoo.com这个域下面的cookies将会影响整个yahoo!站点的响应时间,图2显示了Yahoo!的所有页面中来自.yahoo.com的cookie所占的比例。
图8
大约80%的页面的cookie小于1000bytes,这个会导致5-15ms的DSL带宽速度,数据表明大部分页面没有严重的延迟,但是2%的页面有多于1500bytes的来自.yahoo.com的cookies,尽管2%听起来不多,但是对于Yahoo!这样每天有百万级页面访问的站点,我们有强烈的动机去分析这2%,去排除不必要的cookie,降低cookie的大小,将cookie设置在更小粒度的域中。
在前面关于浏览器缓存使用的文章中,有人在评论中指出关于不同浏览器的影响,因为ie和ff对于cookie的最大容量和数量的支持和实现是不一样的,我们分析数据发现不同的浏览器对于cookie的大小没有特别大的区别,我想进一步的研究不同浏览器对于性能是否有影响将会是非常有趣的 不同站点cookie大小的分析
图9显示了Yahoo!以及一些主流站点的cookies的大小,这个实验中,我们清楚了我们机子上的所有cookie并且只访问这些站点的主页
图9 仅仅访问主页
图9中yahoo的cookie大小为122bytes,和上图中的cookie大小是不一样的,因为这个实验我们只是访问了那几个站点的主页,而前一个实验则是反映了真实的用户,他们访问大量的雅虎页面(主页或者下面的子页面),为了说明这个举个例子,如果tv.yahoo.com 和 movies.yahoo.com要共享一个cookie,那么这个cookie就必须设置在.yahoo.com这个主域中,对于访问了大量雅虎包括子域页面的用户来说他们的cookie的大小要远大于只访问yahoo主页的用户cookie的大小。将cookie设置在合适的域中和cookie的大小一样重要,一个在.yahoo.com中的cookie将会影响yahoo站点买一个页面的响应速度。 要点
- 清除不必要的cookie
- 将cookie的容量尽量最小化
- 要将cookie设置在合适的域中
- 设置合适的cookie过期时间
最大化浏览器的并行下载数量
并行下载
页面中组件的数量对于最终用户的响应时间影响最大,每一个组件需要一个额外的HTTP请求,我们知道浏览器是并行的发送HTTP请求的,你可能会问,为什么HTTP请求的数量会影响响应时间,浏览器为什么不能一次性把所有需要的东西下载完呢?
需要回到HTTP1.1的协议,它建议浏览器每次下载2个组件(一个主机),HTTP的请求是一个阶梯形的,如图1所示
图10
如果一个页面的组件分布的让两个主机进行下载,那么页面的整个响应时间会快2倍,HTTP请求如图2所示,一次下载4个组件(两个主机)。
图11
将每个主机的并行下载数量设置为2是一个基本原则,在默认情况下,ie和ff都遵守这个原则,但是用户可以自己设置这个数量。IE将这个数据存放在注册表中(查看微软帮助),ff可以通过network.http.max-persistent-connections-per-server来设置。
值得一提的是在HTTP/1.0中,FF一个主机的默认并行下载数量是8个,图3显示了如果我们在HTTP/1.0下用FF下载10个图片的情况,虽然只用一个主机,但它比图2(两个主机)显示的都快。
图12
图 13 HTTP/1.0 1.1比较
大部分网页下载使用HTTP1.1协议,但是让并行下载的数量大于2个是非常吸引人的,不要指望用户去修改浏览器的设置,前端工程师工程师应该通过CNAME来将需要下载的组件分开给多个主机,最大化并行下载数量是需要一定开支的,取决于带宽和cpu的速度,太多的并行会适得其反影响性能的。
如果将浏览器的最大并行数量设置为2(单主机基于HTTP/1.1)会怎么样,下面继续讨论 如果我们使用CNAME来增加页面并行的数量会怎么样?
这里有两篇关于这个话题的文章(Ryan Breen of Gomez 和Aaron Hopkins over at Google.),我们雅虎的性能团队也做了相关的实验去检测使用多个主机CNAME产生的影响,这个实验检测一个只有20张图片的页面,这些图片在同一个服务器上面,并且被雅虎真实的页面所访问,我们的实验检测了下载页面的时间,图四所示。
图14
在我们的实验中,我非别使用的CNAME数量为:1,2,4,5和10,他们对应各自的并行下载数量为2,4,8和10,我们获取20张小图片(36p× 36px)和20个大图片(116 x 61 px)。令我们惊奇的是,当我们下载大图片的时候,4个以上CNAME的使得响应时间变得更长;当我们下载小图片的时候,2个以上的CNAME并没有对响应时间产生很大的影响,总体来说,2个CNAME是最好的。
导致响应时间变慢的一个可能的原因就是当我们增加并行下载数量的时候客户端CPU的占用情况,我们并行系在的图片数量越多,客户端的CPU占用率越高,在我的笔记本上,当并行下载数量为2的时候,CPU的占有率为25%,当并行下载数量为20的时候,CPU的占用率为40%,这些CPU数据在不同的用户机子上肯定是不同的,但是这给了我们一个考虑的因素(当我们通过增加CNAME来最大化并行下载数量时)。
这个结果仅仅适用于那些数据已经在浏览器缓存中的站点,如果时站点在缓存中的数据是空的,那么响应时间会随着CNAE的增加变的更坏,对于那些希望提高用户第一次访问的用户的体验的站点,我们建议不要增加CNAME的数量,对于那些已经有缓存的页面,提高并行下载数量会改进响应时间,具体选择哪一个应该根据实际情况而定。
我们的原则是至少是并行数量为2,但是不要使用多于4个CNAME,最主要的:减少页面中组件的数量。
优化IPhone缓存
IPhone改变了用户在移动终端上浏览网页的方式,IPhone给web开发者带来了新的机会的同时,也带来了一些性能的挑战。
IPhone上面存储着有限的信息,知道其上面的cache属性对创建高性能的站点是很有必要的。在前几篇文章中,我们分析了80%的(或者更多)响应时间是如何被前端占据的,分析了为什么cache很重要,针对本文的主题,Yahoo!的性能团队研究了Iphone的cache属性以及性能相关,我们对于IPhone以下的几个cache属性特别感兴趣:
- 对于单一的组件其最大的cache容量是多少
- 对于多个组件其最大的cache容量是多少
- 压缩的组件对于其cache容量的影响
- 机器重启后cache是否还存在
我同时在IPhone和IPod Touch上面做了实验,得出的结论是一直的。
使用Cache还是跳过Cache
在前面,我们讨论了区分空缓存的用户体验和全缓存用户体验的重要性.当有一个外部的组件(脚本,样式,图片)被引用到一个HTNL页面时,当HTML页面会发的浏览器发送HTTP请求并且把组件存入浏览器内存,尽管在挥发的过程中组件是存储在缓存中,但是它可能会存入浏览器缓存,也可能不存入浏览器缓存,“跳过缓存”是指浏览器绕过缓存直接向服务器发HTTP请求来请求需要的组件,“使用缓存”是指在浏览器缓存中找到了需要的组件那么相应的HTTP请求则免掉了。
当组件的HTTP头中包含过期时间或者cache_control时,它是可以可以被缓存的。
Expires: [Expiration time in GMT Format]
Cache-Control: max-age=[Expiration time in seconds]
如果组件不包含任何一个上面的头,则不会被浏览器缓存的。为了测试IPhone浏览器的缓存能力并使其使用缓存,我们在服务器设置了以下头信息:
Expires: Thu, 15 Apr 2010 20:00:00 GMT
最大化cache容量
在我们的实验中,我们为了检测对于单个组件的最大缓存一直在变化着不同组件(脚本,样式,图片)的大小,我们发现如果一个组件大于25KB,IPhone浏览器将不会再缓存这个组件,所以专门对于IPhone的网页,为了更好的利用缓存,我们应该将每一个组件设置在25kb以下。
好消息是,如果浏览器下载的一个组件大于25KB,这个对于已经在缓存中的组件是不会产生影响的。已经从在在缓存中组件只能被新的小于25KB的组件按照LRU(least recently used)算法替换。
Apple的网站对于单个组件有10M的缓存容量,这个是指浏览器可以存储在内存中的容量(不是硬盘),然而IPhone的实际容量是更小的,这个取决于内存以及当前正在运行的程序,没有被缓存的组件将会在页面unload的时候被回收。
为了检测IPhone对于多个组件的最大容量,我们在页面中增加了一些25KB的组件,我们测试了不同类型的组件最后发现IPhone浏览器可以缓存最多19个25KB的组件,所以IPhone的最大缓存容量是475-500KB
压缩组件
我们也分析了压缩组件和非压缩组件对于缓存特性的影响,我很惊奇的发现,25KB的最大容量与一个组件是否压缩是没有关系的,IPhone的Safari浏览器会将组件在保存的缓存之前进行编码,只有组件的大小是主要的,因此,再一次强调将组件变小的重要性
重启
IPhone和IPad用户经常需要重启机器,假设在重启之前用户正在浏览网站,当机器重启之后,图片,样式等组件是不是还继续留在浏览器的缓存之中,我们通过实验发现IPhone浏览器不会再重启之后还保持之前的信息,这说明IPhone的Safari浏览器是通过系统的内存来缓存组件的,而不是通过硬盘来存储。
要点
专门为IPhone用户设计网页,为了提高可用性,你也需要降低页面的体积去增加前端性能。IPhone缓存实验建议对于IPhone的网页:为了更好的利用缓存,将每一个组件的体积减少到25KB以下.
组合文件
在性能探索1中,我们讨论了减少http请求对于响应时间的影响程度,并且减少HTTP请求的次数也是提高性能最容易的方法。一个不用必须简化界面的方法就是将大量的脚本组合成一个脚本文件,同样把大量的样式文件组合成一个样式文件。组合文件将减少额外的HTTP请求,同时也可以减少由TCP等引起的延迟。
图1显示了一个拥有6个单独脚本的页面下载时其时间分配情况,注意,对于每一个文件,浏览器将会单独发送一个HTTP请求去读取,在每个脚本之间的时间空隙表明浏览器需要花时间去解析和加载脚本,图2显示了当将上说的6个脚本文件组合成一个后页面下载时其时间分配情况。
图15 六个单独的脚本文件
图16 组合成一个脚本文件
组合脚本和样式文件在开发过程中是很烦躁的,通常我们是在开发过程中的将代码作为单独的文件放在逻辑模块中,在产品发布前对文件进行组合的,每一个单独文件的改变,都需要将已经组合的大文件重新组合,重新发布,Yahoo!在这个过程中花费的代价也是很大的。
Yahoo!的hotjobs网站因为组合文件将响应时间减少了8%
雅虎性能团队在hotjobs网站做了实验去检测将多个文件压缩为一个文件时响应时间的变化情况,这个实验是有两个组来做的,第一组访问的页面上面有6个没有组合的脚本文件,第二组用户访问的页面将6个脚本文件压缩成了1个。
将六个javascript文件组合成一个,使得平均性能提高了8%,局域网中的用户访问速度提高了5%,而这一切是在没有修改页面设计和页面属性的前提下发生的。
这个实验的前提是所测试的页面已经做过很好的优化了,响应时间的影响因素很多,包括,组合文件的数量,浏览器的缓存等等,这个实验的结论支持以前所说的减少HTTP请求的数量是一个提高用户体验很好的方法。
其他
你也不能将所有的文件组合成一个文件,假设你有一个或多个脚本在多页面中共享,但是其他的文件仅仅用在一个特定的页面,如果你将所有的文件组合成一个文件在整个网站中使用,那么一些页面将会花时间下载他用不到的文件。相反,要注意不同类型的组合文件,你可以将用在每个页面脚本压缩成一个文件,然后为每一个页面或者某一组页面专门单独组合其需要的脚本。
要点
- 为了减少HTTP请求组合脚本和样式
- 注意不同类型的组合文件
- 避免用户下载多于实际需要的文件
- 要注意文件组合的顺序
傲娇的使用Disqus