性能 | iJohn.org

Archive for the ‘性能’ Category

15th
七月 2011

”Twitter搜索现在快3倍啦“
爱因万江斯坦@2011年07月15日 09:49 Post in 系统构架, 性能 No Comments »

在2010年春季,为了服务不断增长的流量、提升最终用户的响应时延和服务的可用性、有能力快速开发新的搜索功能,搜索团队开始重写我们的搜索引擎。作为不努力的一部分,我们发布了新的实时搜索引擎,将后端从Mysql迁到了lucene的实时版。上周我们发布新的前端,替换了Ruby on Rails版:我们称之为Blender的Java服务器。我们很高兴地宣布这些改变使我们的搜索时延下降了3倍,同时也使得我们有能力在接下来的几个月里快速迭搜索特性。

性能收益

Twitter搜索是世界上注量最重的搜索引擎之一,每天服务超过10亿的查询。在我们发布Blender的前一周,日本 #tsunami 产生了很大的查询负载,并产生了查询响应时间的峰值。随着Blender的发布,我们的95%时延减少了3倍,从800毫秒到250毫秒,同时前端的CPU负载降低了一半。我们每台机器的容量能服务10倍的请求增长。这也意味着我们能能够用更少的机器支持同样的请求数,从而降低前端服务的成本。

Twitter搜索现在快3倍啦

blender发布前后的95%的查询接口时延对比图

Twitter改进后的搜索架构

为了弄明白这些性能收益,你首先必须明白我们以前的RoR前端是如何地低效。前端运行固定数量的单线程工作进程,每一个做下面的事情:

. 解析请求
. 同步查询索引服务
. 聚合并展现结果

我们很早就知道,同步方式处理请求会低效地使用我们的CPU。经过一段时间,在Ruby的代码层累积欠了重大的技术债,这样很难增加新特性和改进我们搜索系统的可靠性。

Blender通过下面的方式解决了这些问题:

 创建了一个完全异步的聚合服务。没有线程会去等待网络IO完成
. 从后端服务中聚合结果,例如,实时的,最流行的微推和地理分片
. 优雅的服务之间的依赖关系处理,工作流自动处理后端服务之间的传递性依赖

下图显示了Twitter的搜索引擎架构。从网站、接口或内部客户过来的查询请求通过硬件负载均衡设备转发到Blender上。Blender解析请求,然页转发给后端服务,使用工作流来处理服务之间的依赖。最后,将后端服务返回的结果进行合并,以合适的语言呈现给客户。

Twitter搜索现在快3倍啦
Twitter跟Blender有关的搜索架构图

Blender简介

Blender基于Netty http://www.jboss.org/netty打造的Thrift和HTTP服务,Netty是用Java写的高扩展的客户和服务器库,基于它能快速地开发各种协议的客户和服务器端程序。我们没有用其它产品,如Mina和Jetty,主要是因为在其它项目中已经用过它,当然它的接口更简洁、文档也写得不错,这也是我们用它的原因之一。为了让Netty跟Trift一起工作,我们写了解析Thift的代码,专门解析从Netty的channel缓存中的过来的Thift请求,当它从socket读请求或需要封装Thrift响应时,都会调用它些代码。

Netty定义名叫通道的抽象功能,用来封装到网络套接字的连接,提供读、写和连接等IO请求的接口。所有通道的IO请求天然都是异步的。这意味着任何IO调用立即返回一个通道实例对象,以通知请求是否完成、失败或被取消。

当Netty服务器收到一个新的连接,它会创建一个通道流水线来处理这个连接。一个通道流水线不是别的,其实就是通道处理程序的序列,通过它实现处理连接的业务逻辑。在随后的一个小节里,我们会说一下Blender是如何将这些通道流水线映射到查询的工作流处理上的。

工作流框架

对于Blender来说,一个工作流就是一组后端服务,服务之间有依赖关系,每个后端服务都需要处理才能处理收到的查询请求。Blender自动解决后端服务之间的依赖,例如,如果服务A依赖于服务B,首先会请求A,把它返回的结果返回给B。工作流可以很方便地用有向无环图来表示(见下图)

Twitter搜索现在快3倍啦
6个后端服务的Blender工作流示例图

在上面的示例图中,我们有6个服务{s1,s2,s3,s4,s5,s6},之间有依赖。从S3到S1的边意味着先要访问S3,之后才是S1,因为访问S1时需要访问S3后的结果。对于这样的一个工作流,Blender框架先会进行拓扑排序以确实所有服务的顺序,它们也必須以这样的顺序依次调用。上图的服务执行顺序将是{(s3,s4),(s1,s5,s6),(s2)}。这也表明s3和s3可以在第一轮里并发调用,一旦它们返回结果,S1、S5和S6可以在第二轮里并发调用,最后才调用S2。

一旦Blender确定了工作流的执行顺序,工作流将会映射成Netty的流水线。流水线是一个处理程序序列,请求依次会通过这些处理程序。

复用进来的请求

因为工作流映射成了Netty的管道,我们需要将进来的客户端请求转发给合适的流水线。因为这个原因,我们设置了一个代理层,它负现复用并转发客户端请求到流水线上:

. 当一个远程Thrift客户端跟Blender建立一个长连接时,代理层会创建一组本地的客户,跟每个本地的工作流服务对应。注意当Blender进程起动时,会在同一个java虚拟机里起动所有的本地工作流服务
. 当从套接字上收到一个请求时,代理层读取这个请求,计算出需要请求那个工作流服务,然后转发给对应的工作流服务
. 类似地,当收到本地的工作流服务的响应时,代理层读取结果,并把结果转发给远程的客户

我们利用Netty的事件驱动模型来异步完成上面的所有任务,这样也没有任何线程需要等待IO。

分配后端请求

一旦请求转到了工作流流水线,将依次由工作流定义好的服务处理程序处理。每个服务处理程序创建一个合适的后端请求,转发给远程的服务器。例如,实时处理程序会创建一个实时搜索请求并异步地将它发给一个或多个实时索引服务器。我们使用twitter commons库https://github.com/twitter/commons(最近开源了!)来提供连接池管理、负载均衡和死主机检测。

当请求分配给所有后端之后,请求对应的IO线程就将被释放。一个定时线程每隔几毫秒就检查有没有后端的请求返回,并设置对应的标记,表明请求成功、超时或者失败了。在一个查询请求的存在周期内我们为它维持一个对象,用来记录上面这些状态信息。

成功的响应会聚合在一起,再发给工作流中的下一批处理程序。当第一批所有请求的响应的返回之后,将异步地发出第二批请求。这个过程会一直重复直到我们完成工作流或者超出工作流设置的超时时间。

你能够看出,在工作流执行期间内,没有任何线程会去等待IO。这让我们有效地利用Blender机器的CPU,并且处理大量的并发请求。我们能将大量请求并发发给后端去执行,从而也能减少请求的时延。

Blender部署及以后的工作

为了在我们的系统中增加Blender的时候,能确保提供高质量的服务。我们用老的RoR前端作为代理,转发请求给Blender集群。这样使得我们在对后边的技术进行大的调整时,给用户一个一致服务体验。我们下一阶段部署时,会从搜索的服务栈中完全去掉RoR,让用户直接去连接Blender,从而进一步降低访问时延。

原文链接:
Twitter Search is Now 3x Faster

9th
六月 2011

Optimizing MongoDB: Lessons Learned at Localytics
爱因万江斯坦@2011年06月09日 21:38 Post in 性能 No Comments »

21st
四月 2011

天涯社区的进化经验(一)技术篇
爱因万江斯坦@2011年04月21日 13:05 Post in 系统构架, 性能 No Comments »

设备投入
– 1999 1台服务器
• 2000年,在线用户300
• 2002年,在线用户1000
• 2003年,在线用户8000
WEB+DB共用在一台服务器上
– 2004 4台WEB 2台DB
• 2004年,在线用户2万
WEB及DB剥离。同时WEB不断增加,DB进行分库(4台WEB,2台DB)
– 2005 7台WEB 4台DB
• 2005年,在线用户14万
– 2006 11台WEB 4台squid(页面缓存) 6台DB
• 2006年,在线用户20万
– 2007 +双线接入 F5设备
实现双线双IP,购置F5实现本地及全局负载均衡,haproxy部署,动静剥离
– 现在 136台应用 38台DB
2008年,数据库双机(事务复制),IBM架构咨询。
• 2009年,在线用户40万
Memcache,数据访问层,服务器虚拟化。
• 2010年,在线用户54万

大多数网站发展初期遇到的技术问题,不是没有采用多层架构、没有使用内存缓存、没有购买好的服务器,而是数据结构及检索设计出了问题。
数据库调优是一个长期的过程,建立模拟环境,来跟踪每条请求的反应时间。

数据库设计注意事项
• 在对产品及业务深度了解的前提下进行数据库设计
• 表记录规模的增加对查询不要有性能问题
• 合理的索引(不要犯常识性错误)
• 慎用联合查询(几乎不用),通过一定的数据冗余来回避联合查询
• 必要时通过合并数据,减少结果集大小
• 尽量通过WHERE来定位分页数据
• 在设计时尽量考虑数据规模增长后的拆分问题

WEB服务扩展
• 无状态服务
– 不要用session保留用户数据,Cookie(或会话Cookie)保留用户标示,用户数据统一保留在数据库,或内存中)
• 应用拆分
– 按不同业务拆分,按流量拆分
• DNS轮询、或通过应用控制轮询

构架完善:
双链路接入双IP
– 使用了2台F5-GTM-1500做链路判断。
• 负载均衡
– 使用了2台F5-LTM-6400实现负载均衡
– LVS(IP层负载均衡)
– 反向代理(haproxy 7层负载均衡
• 动静分离
– 使用lighttpd来提供静态资源服务。
• 代理服务
– 使用haproxy提供七层负载均衡,通过ACL拆分应用。
• 页面缓存
– 2005年,使用squid-2.6在前端实现页面级缓存。
– 已经替换成varnish,增加压缩模块。
• 内存缓存
– Memcache

建立服务可用性监控
• 要预警及报警机制和故障点快速定位
– 应用运行中的错误监测
– 应用可用性监测
– 服务器及网络设备健康监控
– 网络流量监控
– 全国访问质量监测

27th
十二月 2010

Sun JDK 1.6内存管理 -调优篇
爱因万江斯坦@2010年12月27日 23:23 Post in 性能 No Comments »


27th
十二月 2010

Sun jdk 1.6内存管理 -使用篇
爱因万江斯坦@2010年12月27日 22:14 Post in 性能 No Comments »

26th
六月 2009

Even Faster Web Sites: Performance Best Practices for Web Developers
爱因万江斯坦@2009年06月26日 23:57 Post in 性能 No Comments »

这是Steve Souders继《High Performance Web Sites》 后,于今年推出的另一部新书:《Even Faster Web Sites: Performance Best Practices for Web Developers》,其实这书有六个章节是由另外八位业内专家所写:如Ajaxina.com的联合创始人Dion AlmaerBen Galbraith,JSon的创造者Douglas Crockford,还有YSlow,YUI的开发工程师等,如果说《High Performance Web Sites》定义了一般网站所通用的14条法则,而这本Even Faster Web Sites则是专对web2.0网站的一个优化的最佳实践的经验总结。

本书主要分三部分,第1到7章节是讲JavaScript的优化实践,第8到12章是讲网络性能优化实践,第13,14章是讲浏览器的具体性能优化实践,而附录部分则是对性能分析工具使用的一个介绍。

Even Faster Web Sites pdf电子版下载(右键另存为)

 

22nd
十一月 2008

翻译:High Performance Web Sites(8)-Chapter 6
爱因万江斯坦@2008年11月22日 23:33 Post in 性能 No Comments »

《High Performance Web Sites》 :Chapter 6

法则6:把script放到页面的下端

第五章我们将样式表放置于HTML的HEAD中以加快页面渲染。其实script也有类似的问题,但解决的方法正好相反:把script放到页面的下端,会利于页面的快速渲染。

Script所带来的问题

为了更好的说明这第6条法则,我们先看一个反例吧。把script放在HTML页面的中间位置,看看这种方式页面的下载情况,如:http://stevesouders.com/hpws/js-middle.php
这个script是睡眠10秒钟然后启动,我们访问一下这个页面,发现整个HTML已经全部download完毕,但页面在渲染到中间时,停住了,下一半仍是空白的未渲染区域。直到这个js睡眠的10秒过后,另一半页面的内容才开始渲染和下载后面所需的图片等组件。很显然,这个放在HTML中间的script阻碍了后面页面元素的下载,还阻碍了页面的渲染。
这也就是为什么我们要把所有的样式表放到HTML页面的上端HEAD处,它们才会被先下载,不会阻塞页面的渲染。而script后面的页面内容则会等待script执行完毕,才开始渲染。所以把script放置的越下,更多的页面内容才会更快的渲染。
并行下载
对下载速度影响最大的就是页面组件的数量。前几章介绍过,如果当前浏览器的缓存是空的,则每一个组件都产生一个HTTP请求。你可能会问:那为什么浏览器不一次性的请求所有的组件呢,非得一个一个的去请求?
要解释这个,就要说到HTTP/1.1规范了,它建议浏览器一次对同一个域名最多只发起2个并行下载请求.(http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4)
很多的页面连同其所包含的组件都是一个域名下的,如下图所示的组件下载的阶梯图 6-1:

 

如果一个页面的组件是分布在两个域名下的,那它的响应时间总体来说应该会快两倍,如图6-2所示的情况,会有四个组件并行下载。
通常情况下IE与Firefox都遵循了HTTP/1.1规范关于同一个域名每次只能有两个并行下载请求的建议。如果想扩展IE的下载束缚,可以参考:微软网站的文章:How to configure Internet Explorer to have more than two download sessions,” http://support.microsoft.com/?kbid=282402.
而要改Firefox的默认设置,则可以在配置页面中修改network.http.maxpersistent-connections-per-server属性.有意思的是,在对HTTP/1.0的支持中,Firefox默认的是每个域名下同时可有八个并行下载请求,也就是说可一次同时下载8个页面组件,这可比IE的默认值要大方多了。如图6-3就是显示的在Firefox下下载一个页面的示意图,这可要比6-2所示的看起来爽多了。

 

现在大多数的网站都遵循HTTP/1.1规范,增加每个域名下的并行下载数量是个办法,但我们总不能都指望着用户去调整自己浏览器的设置参数。前端工程师应该考虑把页面组件放在不同的域名下,增加并行下载最多也只是消耗一点客户端的CPU资源和带宽,除非是大量的并行下载才会影响到性能。在Yahoo!的实验下,我们发现把页面组件放在两个域名下要比划分成1,4或10个域名性能要好,具体可参见(Tenni Theurer, “Performance Research, Part 4: Maximizing Parallel Downloads in the Carpool Lane,”http://yuiblog.com/blog/2007/04/11/performance-research-part-4/.)
Script是怎么阻碍下载的
组件并行下载的好处我们都知道了。但script这个东西却可能会破坏这个和谐社会:因为script可能会用document.write语句来改变页面内容,所以浏览器会等待script执行完毕。
另一个原因是浏览器要保证script的执行顺序,先出现的script要先执行,哪怕后面的script的k数有多么小,也要等着,因为script在功能上可能会有前后关系,后面的script可能会用到前面script的某些操作。
来个例子说明
http://stevesouders.com/hpws/js-blocking.php
这个页面所包含的组件出现顺序如下:
1,host1下的图片A1
2,host2下的图片B1
3,host1下的script,等待10秒完成
4,host1下的图片A2
5,host2下的图片B2
看看是host1下的两张图片同时下载?还是其他?

 

IE和Firefox都会先下载host1和host2的图片,然后开始下载host1上的script,直到这个script下载完成,才会开始下载后面的两个图片。
最糟糕的情况:script在页面的顶部
 

在这种情况下将会导致页面是个空白:
• Script下的文字页面内容会被阻止渲染。
• Script下的组件也会被阻止下载
直到这个script下载并执行完毕才会解除阻止。给个实例:http://stevesouders.com/hpws/js-top.php
因为script在顶部,整个页面的渲染被阻止了,于是这个页面将会呈现一个我们在第五章中解释的空白屏。而逐步渲染是一个很关键的良好用户体验。图6-5就是这个例子的页面下载阶梯图,后面的图片组件都被阻止了。

 

最好的情况:script在页面的底部
放置script最佳的地方当然是页面的底部了。页面的内容不会被阻止渲染,可视组件能依次下载渲染。如实例http://stevesouders.com/hpws/js-bottom.php,图6-6就显示了这种方式script对页面的影响是最小的,我们可以对比一下图6-5看看,再给一个具体的比较实例吧:http://stevesouders.com/hpws/move-scripts.php是不是感觉更明显了。

 

这也不是说所有的scrript就应该放到页面底部了,如果是包含有document.write这样影响页面渲染内容的script,是不应该被放到最下面的,所以还是要以实际的页面功能、逻辑等方面来考虑,通常情况下,只要是不涉及强制更改页面渲染的js,都应该放到页面底部。
另外有一种方式是http://stevesouders.com/hpws/js-defer.php所示的defer方式,在script定义时声明,但这种只对IE有效,对firefox无效,大家知道一下就行了。

 

16th
三月 2008

翻译:High Performance Web Sites(6)-Chapter 4
爱因万江斯坦@2008年03月16日 22:58 Post in 性能 No Comments »

《High Performance Web Sites》 :Chapter 4

法则4:使用Gzip压缩组件  Gzip Components

前端开发工程师能力的好坏直接关系着页面的访问快慢(John:要知道,用极其丑陋的table套table做出的页面比简洁的div与table做出的页面无论是从K字节上,还是载入速度上都是极大的消耗),但另一些如用户的带宽等我们无法控制的因素,也会影响到用户访问您web应用的速度。法则1法则3介绍了如何通过减少不必要的HTTP请求来提升页面加载时间,法则2介绍了用CDN,可以让我们离用户更近,但我们总不能把所有的HTTP请求都优化掉吧。于是,我们这法则4应潮流而生。

法则4,是通过减少HTTP响应的数据量来加快响应速度。要知道如果一个HTTP的响应足够少,那传输的时间多快呀,因为从服务器端传送到浏览器端的数据包小了,也少了,岂有不快之理。这对像那些网络不太好的用户可是特有效。这一章节就会展示如何通过gzip去压缩HTTP响应,这是减少数据量最easy的方法,但有得到就会有付出,gzip也会有一些不好的影响,这个后面介绍。


压缩是如何工作的

很久以前我们就把文件压缩应用在电子邮件和FTP站点当中。直到HTTP/1.1的规范制定,web客户端才开始通过HTTP请求中的 Accept-Encoding 头信息来来表示其支持压缩。

如果web服务器在请求信息中看到这个头信息,它就可以通过响应的 Content-Encoding头信息来返回服务器可用的压缩方式。
gzip是目前最流行和高效的压缩方式。它是一个可以自由使用,不受专利权限制的压缩格式,是由GNU项目开发出来并定义在RFC1952规范中。另外还有一种压缩方式是deflate,但它不如Gzip那么常用和高效,其实上,我只发现一家网站在使用deflate:msn.com 。而支持deflate数据压缩的浏览器,也支持Gzip,所以Gzip是非常好的选择。
 

该压缩什么

服务器要压缩什么是取决于文件格式。很多网站只压缩HTML文件,其实script和样式表文件的也都值得压缩(事实上,任何文本型的数据都可以压缩,包括XML和JSON数据)。图片和PDF文件的则不必再用gzip压缩,因为它们本身就是压缩格式,再多余的压缩只会浪费CPU资源,也得不到多少的文件压缩量。

gzip压缩需要消耗服务器端的CPU,而客户端也需要执行解压gzip格式的数据。至于是否值得压缩的问题,我觉得任何超过1K或2K的文件都值得压缩。mod_gzip_minimum_file_size 参数就可以配置您想要压缩的文件的大小范围,它的默认值是500 bytes.

老套路,我又调查了美国10大网站使用gzip的情况如下图。有9家都用gzip压缩他们的HTML文件,7家还用gzip压缩了script和样式表文件,其中有5家是完全压缩了所有的script和样式表。一个网站如果压缩所有的HTML文件,样式表,script文件,甚至可以减少70%的数据量,这是我们下面的段落里会提到的。

节省The Savingsgzip压缩后,返回的数据量一般会减少70%,如下图所示,显示了scrtip,样式表文件在压缩前和压缩后的数据量的变化,同时也显示了如果用deflate方式来压缩的数据量对比。

很显然,从上图中就知道我们为什么要选择gzip了。gzip会减少约66%的数据量,而deflate只会减少约60%。

配置 Configuration

配置gzip的模块要取决于您Apache的版本:Apache 1.3使用的是mod_gzip,而 Apache 2.x 使用 的是mod_deflate。这一段落就会讲解如何配置每个模块。

Apache 1.3: mod_gzip

Apache1.3的gzip压缩功能是通过 mod_gzip 模块来现实的。mod_gzip 有很多的配置参数,这些在mod_gzip的站点上有说明,在这里我介绍一些最常用的参数。

mod_gzip_on
开启mod_gzip.
mod_gzip_item_include
mod_gzip_item_exclude
定义要被gzip压缩或不压缩的文件的类型,MIME类型, user agent, 等等。

大部分的web服务器都会打开mod_gzip,并把text/html设为默认文件类型。最重要的是您应该为script与样式表文件也打开gzip功能。您可以参考下面对Apache 1.3 的配置:

mod_gzip_item_include  file    \.js$
mod_gzip_item_include  mime  ^application/x-javascript$
mod_gzip_item_include  file    \.css$
mod_gzip_item_include  mime  ^text/css$
gzip的命令行程序可以控制压缩的级别以及CPU的使用率,但在mod_gzip中却无法配置这些。有时对数据流的压缩会造成CPU的过分负荷,我们可以选择缓存压缩后的响应数据到磁盘或是内存中,如果要手动实现这一功能就太麻烦了,还好,我们可以通过mod_gzip来自动保存压缩后数据到磁盘上,还可以及时的响应更新。实现这一功能则要配置 mod_gzip_can_negotiate 和 mod_gzip_update_static 参数。

Apache 2.x: mod_deflate
 

Apache 2.x 是通过mod_deflate 模块来实现压缩。尽管它的名字是deflate,但实际它是实现的gzip压缩方式(John:怪异吧!)。如果要实现像对script和样式表的压缩这种基本的配置只需要下面这一行就够了:

AddOutputFilterByType   DEFLATE      text/html    text/css    application/x-javascript

与mod_gzip不同,mod_deflate可以通过参数配置来控制压缩级别。要获取更多的信息,可查阅Apache 2.0 mod_deflate的文档。

代理缓存  Proxy Caching

以上介绍的简单配置对直接访问web服务器的方式来说没有一点问题,web服务器通过请求中的Accept-Encoding头信息来判断客户端是否可以支持压缩的数据,然后把压缩或没压缩的数据再返回给客户端,这些都是通过HTTP的header头信息来实现的。

但如果用户是通过代理上网的,那用户的请求也都是通过代理服务器中转发送的,这时就比较麻烦了:假如用户发请求给代理服务器时,并表明其不支持gzip,而代理服务器再中转数据时却将已经缓存过的压缩的数据返回给用户,这时就会造成用户看到的是一堆乱码,反之亦然。

要解决这个问题则要在服务器端加上vary头信息。mod_gzip默认是为所有的响应都增加Vary:Accept Encoding头信息,这样代理服务器就会缓存压缩和未压缩版的数据了。

特殊情况 Edge Cases

目前90%的浏览器都支持gzip。但还是会有一些特殊情况我们不得不考虑,如在早期的IE版本中,特别是IE5.5和IE6.0 sp1存在一些bug,一种比较安全的做法是用”浏览器白名单(browser whitelist)“,让服务器端只针对一部分浏览器发送gzip格式的数据。如下的设置就是只针对IE6~9的版本和Mozila 5~9的版本发送gzip数据。

Apache 1.3使用User-Agent:

mod_gzip_item_include  reqheader  ”User-Agent: MSIE [6-9]”
mod_gzip_item_include  reqheader  “User-Agent: Mozilla/[5-9]“
Apache 2.x 使用 BrowserMatch :
BrowserMatch  ^MSIE [6-9] gzip
BrowserMatch  ^Mozilla/[5-9] gzip

而针对代理缓存,一种方法是在Vary的头信息中加上User-Agent标识来告诉代理服务器,我们使用了白名单;在mod_gzip模块中,如果它发现配置了白名单,则会自动为Vary加上上User-Agent标识。但是不要指望代理服务器会缓存所有的浏览器白名单的每一个url副本。所以我们不得不采用另一个方法:为返回的响应中加上Vary : * 或 Cache-Control:private 头信息,以表明完全禁止代理服务器的缓存,不让代理去缓存任何一个组件,让用户通过代理时,都要去请求真正的服务器以获得页面数据,事实上Google和Yahoo!都是使用的这一策略,尽管它会带来更多的流量消耗。

还有一个特殊情况要指出,就是ETags(我们将会在Chapter 13章节单独提到),默认情况下它都不会被压缩,所以最好的办法就是干脆禁用ETags,这个还是放在Chapter  13章节再详谈吧。


下面三个链接分别是没有gzip压缩,只压缩了HTML和压缩了全部组件的三个链接页面:
Nothing Gzipped
http://stevesouders.com/hpws/nogzip.html
HTML Gzipped
http://stevesouders.com/hpws/gzip-html.html
Everything Gzipped
http://stevesouders.com/hpws/gzip-all.html
下图就是三者的具体数据量对比和时间对比:

 

9th
三月 2008

翻译:High Performance Web Sites(5)-Chapter 3
爱因万江斯坦@2008年03月09日 22:54 Post in 性能 No Comments »

《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请求数还有不必要的数据传输。

相关章节:

 

6th
三月 2008

翻译:High Performance Web Sites(4)-Chapter 2
爱因万江斯坦@2008年03月06日 22:39 Post in 性能 No Comments »

《High Performance Web Sites》 :Chapter 2

法则 2: 使用CDN 内容分发网络  Use a Content Delivery Network

用户的网络带宽在逐年增加,但用户访问您web服务器的快慢仍受着地域的限制(John:最典型的例子就是我们大陆的南北电信互通问题)。Web创业者往往都会在某一地域的机房放置自己的服务器,但一旦他们渡过艰难的初创阶段,开始要面对涌入的大量用户时,他们都要面对这样的现实:即一个地域机房里的那一台服务器已经不足于应付这种大访问了,是时候在多个分散地域(城市结点)上部署更多的内容服务器。

如何迈出第一步呢?要实施地理上的内容分布,切勿急着尝试以分布式的系统构架去重新设计您的web应用。如果依赖于应用系统的分布,那您的重构工作可能会是十分艰巨,如要处理会话状态的同步和在多个地域的数据库之间处理数据一致性等复杂问题(John:如果您进行的是类似银行级的用户应用,则不在此讨论之列)。您应该优先考虑缩短用户与您web内容之间的地域距离。

还记得我们在前面的Chapter A中讨论的性能黄金法则吗:
只有10-20%的最终用户响应时间是花在了下载HTML源文件上。其它的80-90%是花在了下载页面中的所有组件上。

如果您的web应用服务器离用户足够近,那么在Chapter A 中讨论的那个第一个HTTP请求的响应时间就可以更快;再或者您的web组件服务器也离用户足够近,那在页面中请求这些组件的响应时间也会缩短,这可比您重新设计一个分布式的系统的复杂度要小的多了,这就要用到内容分发网络:CDN(content delivery networks)。

Content Delivery Networks

内容分发网络 CDN 是一个分布在多个地域间用于为用户提供更高效的内容访问服务的网络服务器集群。这种高效不仅体现在性能上,也体现在成本的节省上,因为CDN会选择离请求用户物理结点最近的一台服务器为用户提供内容访问服务,以在到提高响应时间。

一些大型的互联网公司都拥有自己的CDN,但使用专业CDN服务提供商的服务则更能节约成本。如Akamai科技公司于2005年收购了Speedera网络公司,成为北美CDN市场的领军者,还有Mirror Image网络公司,Limelight 网络公司,SAVVIS 公司等。

如下图2-1是十大美国网站使用CDN的情况:

 

我们能看到:
• 有五家网站使用了Akamai
• 有三家分别选择了 Mirror Image,Limelight,SAVVIS
• 其他的五家要么没有使用CDN,要么就是有自己的CDN解决方案

规模较小或是非商业网站未必能负担得起CDN服务器的高额费用,但有一些免费的CDN服务机构可以选择。如部署在阿姆斯特丹自由大学的基于Apache模块的Globule(http://www.globule.org),建在普林斯顿大学的CoDeeN (http://codeen.cs.princeton.edu)等等。(John:老实说,这些免费的CDN对我们没有什么用)
CDN除了能提升响应时间,还有一个好处。CDN还可以用于备份数据,扩充存储容量甚至是做缓存;它还可以帮助我们解决流量高峰的问题,如,一个提供实时性较强的天气预报或财经新闻的服务,或是发布某种体育娱乐新闻时,这种瞬间的访问高流量也可以被CDN所分流。

说到这,该说说CDN的缺点了,除了费用高,您的服务响应时间则会依赖于CDN服务商的硬件和同时在使用这家CDN的其它网站,甚至是有可能会受您竞争者的影响,如果您的竞争者也在使用与您相同的CDN服务商。因为CDN服务商一般都是在他所有的分发服务器之间为客户共享这些资源(John:这和大家在机房使用共享的虚拟主机是一个道理)。CDN的另一个不足是您可能会无法直接控制这些内容服务器,假如您要修改所有该CDN中的组件的HTTP响应头,这可能必须通过CDN服务商而不是您自己的技术人员。如果CDN本身出了问题,也会直接影响用户对您的web的访问。在上图2-1中,eBay和MySpace都使用了两家CDN服务商,这可是个明智之举,减少一定的风险,前提是您有足够的业务需求和金钱的支撑。

CDN是用来分发静态内容的,如图片,scripts,样式表或Flash。而要提供动态的HTML页面则要一定的服务器端要求:数据库连接,状态管理,权限认证,硬件和操作系统优化等,这些复杂的东西都超出了CDN的服务器范围(John:CND不是虚拟主机提供商)。由于CDN只提供静态文件的访问,从另一方面看,也使得它更有针对性,它只针对于提供静态文件较多的网站使用。

时间,节省

使用CDN到底能带来多少速度的提升。下面的两个链接页面,都包含相同的页面元素:5个script,1个样式表,8个图片。第一个链接页面是放在Akamai公司的CDN上,另一个是放置在一台单独的web服务器上:
CDN
http://stevesouders.com/hpws/ex-cdn.php
No CDN
http://stevesouders.com/hpws/ex-nocdn.php

由于这是放在美国的服务器,我们在国内访问可能还没那么快,但从测试的时间上来看,用 CDN的页面比放在单独服务器上的要快近18%,当然了,这一结果要取决于您的带宽接入速率和所在的地域。(John:目前的几大门户网站,新华网,人民网等以新闻资讯为主的网站都使用了CDN,在我前面介绍的Youtube构架中,也提到了他们的CDN策略。)

相关章节:

中文翻译:
翻译:High Performance Web Sites(1)-Chapter A 前端性能的重要性
翻译:High Performance Web Sites(2)-Chapter B HTTP 概述
翻译:High Performance Web Sites(3)-Chapter 1    法则 1: 尽可能减少HTTP请求数
翻译:High Performance Web Sites(4)-Chapter 2   法则 2: 使用CDN 内容分发网络
翻译:High Performance Web Sites(5)-Chapter 3   法则3:增加Expires Header
翻译:High Performance Web Sites(6)-Chapter 4   法则4:使用Gzip压缩组件
翻译:High Performance Web Sites(7)-Chapter 5  法则5:将样式表放到HEAD中
翻译:High Performance Web Sites(8)-Chapter 6    法则6:把script放到页面的底部