人人都能做的性能优化 web前端优化
Web优化已经越趋成熟,不再那么扑朔迷离。在这里,我们跟据一些优化实践准则应用于perfgeeks,并进行了记录。Pefgeeks的系统软环境是CentOS5.3 + Apache2.2.3 + Wordpress2.9。优化的工作方式,一般都是:快照 + 分析 + 变更 + 快照。让我们开始吧…
看到这份快照,我们就可以形成一个最简单的统计:
1,请求数量:8 = 5 + 2 + 1 (5个图片、2个CSS文件、1个html文档)
2,发送包大小: 3400bytes
3,响应包大小: 146081bytes
4,页面加载时间: 10.296秒
合并外置Javascript文件或CSS文件
我们通过快照1的观察,很容易发现这里一共加载了二个CSS文件。其中codesnippet.css文件很小,1kb都不到。并且,该文件用于文本代码高亮显示插件codesnippet。而高文本代码高亮显示,几乎每个页面都会用到(考滤到这是一个技术blog)。所以,我们准备把codesnippet.css合并到style.css这个文件中去。并且把插件用于加载该css文件的代码行注释掉。然后,我们再来观察结果
看到这份快照,我们可依然可以形成一个统计:
1,请求数量:7 = 5 + 1 + 1 (5个图片,1个CSS文件,1个html文档)
2,发送包大小:3032bytes。我们会发现总体而言发送包的大小变小了,这是因为我们少发了一个http请求。但是细心的朋友会观察到第一个http请求包从367bytes增加到了403bytes,这是为什么呢?这是因为请求包中多了Cookie数据。用户端通过http请求头Cookie将用户端的Cookie数据传给服务端。
3,响应包大小:145635byes。我们图片请求大小都没有变化,而第一个请求index.php稍微有点变化,这是一个动态资源请求,有点小波动是正常地。我们最关心的是style.css这个响应,它的响应数据包变大了。主要是因为把codesnippet.css合并到了style.css这个文件,所以,这个http响应body部分变大了。7547 + 971 = 8518 (约等于8259),大于8259是正确地,因为还要减去codesnippet.css响应头部大小。
4,页面加载时间:10.446秒。这里时间没有变化,反而加大了,是因为网络的问题。
Expires浏览器缓存服务端响应
浏览器总会把最近访问资源的响应拷贝一份存放在客户端本地,我们称之为缓存对象。如果下一次访问的时候,该缓存对象还是新鲜的,则浏览器不会向服务端发出请求,而是直接渲染给用户。判断本地拷贝(即缓存对象)是否过期过状态,还是保鲜状态。浏览器会通过用户端本地时间与缓存对象的Expires时间相比较,如果还没有到达Expires时间,则认为对象是新鲜的,没有过期。
Expires时间,由web服务器响应的时候指定。Apache2可以使用mod_expires模块设置Expires时间。操作如下:
#lsof -p `cat /var/run/httpd.pid` |grep mod_expires httpd 16554 root mem REG 8,3 9660 784296 /usr/lib/httpd/modules/mod_expires.so
我们查看发现,当前httpd守护进程加载了mod_expires模块。如此,我们可以配置Apache,设置静态资源(image/*, text/css, text/javascript)响应都在用户端缓存一个月。
<IfModule mod_expires.c> ExpiresActive On ExpiresByType image/* "access plus 1 month" ExpiresByType text/css "access plus 1 month" ExpiresByType text/javascript "access plus 1 month" ExpiresByType application/x-javascript "access plus 1 month" </IfModule>
这次的产生的效果很明显,我们明显可以看到,页面加载时间明显降低了。静态资源部分都被缓存(cache)了,所谓Cache就是指请求包大小为0,响应包大小为0。这意味着,用户端根本没有向服务器发出http请求。这里看到所花费有时间,是浏览器解析渲染缓存对象给用户所花费的时间。注意,这里变化很明显地一个地方是http响应状态由200变成了Cache。我们知道,httpd响应状态是不包括Cache的,所以这里的Cache应该是HttpWatch输出,表示使用缓存对象。我们也可以从响应头里面确认Expires时间与响应时间相差正是1个月。
gzip压缩文本类型http响应
我们常常使用zip, gzip,zlib等工具来压缩我们的文本文件。同样的,我们可以使用gzip来压缩类型为text的http响应数据包。Gzip可以让整个http响应缩小大约66%。自Apache2开始,Apache开始默认安装deflate.so模块,虽然名称上是deflate,但实际上还是使用了gzip。操作如下
# lsof -p `cat /var/run/httpd.pid ` |grep deflate httpd 15223 root mem REG 8,3 17916 784291 /usr/lib/httpd/modules/mod_deflate.so
可以知道,当前运行的httpd守护进程已经加载了mod_deflate模块。http.conf配置如下
<IfModule mod_deflate.c> SetOutputFilter DEFLATE SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|iso|tar|bz2|sit|rar)$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|jpg|ico|png)$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.flv$ no-gzip dont-vary BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html Header append Vary User-Agent env=!dont-vary </IfModule>
上面的配置忽略了二进制、图片、PDF, FLV等次源请求的压缩。我们提过,主要压缩的还是响应体类型为文本类型的http响应。
我们再来观察快照的变化。最大的变化是第一个响应体大小从40142bytes降低到了10883bytes,而另一个text/css类型的响应style.css,则从合并后的8259bytes压缩到了2723bytes。用户端接受到的总体大小也从145635byes降到了111200byes。
精简Javascript和CSS脚本
我们知道,JavaScript和CSS代码都是客户端解析的,而文件却存放在服务端。所以,对于js和css文件,用户端浏览器总是通过http协议把js和css文件下载到用户端本地,浏览器再解析js和css文件。对于访问用户而言,javascript和css这些专业概念,应该保持透明。所以,聪明的开发者就会使用精简工具将js和css精简到最小,让下载速度更快。我们推荐yuicompress来精简你的js和css代码。并且,我在这里写了一个脚本,帮助项目发布的时候自动化精简js和css。
#!/bin/sh #@file yc.sh src_path="/var/www/vhosts /perfgeeks.com/wp-content/themes/perfgeeks" out_path="/tmp/output" yui_compress_tool='/opt/tools/yuicompressor-2.4.2.jar' is_ok=1 #make the output directory if [ ! -d "$out_path" ]; then mkdir -p "$out_path" if [ $? -ne 0 ]; then echo "the $out_put was not existed" exit fi fi find "$out_path" -type f -print |xargs rm -rf for ext in `echo "css js"`; do for f in `find "${src_path}" -name "*.$ext" -type f `; do dest_path="${out_path}`dirname $f`" filename=`basename $f` if [ ! -d "$dest_path" ]; then mkdir -p "$dest_path" fi java -jar "$yui_compress_tool" $f -o "${dest_path}/${filename}" --type "$ext" if [ $? -ne 0 ]; then is_ok=0 exit fi done done
我们观察可以知道,这里只有一个style.css需要精简。我们将精简后style响应与精简之前的快照进行对比。
在这里,我们比较可以,虽然变化不是很大,但还是可以看得出style.css响应包还是变小了。这里变化很小主要是因为:我们style.css被我们的前端工程师精简过一次,这次精简的部分只是合并过去的codesnippet.css那部分代码。不然,精简js和css脚本文件带来的变化应该会更大,尤其是大型网站。
优化图片资源:压缩图片
一个页面的组件用得最多的除了js和css外,还有图片。而且图片资源比较大,也是最占页面下载时间的。我们可以通过一些工具来无损压缩图片资源。使图片类型的响应体缩小,达到缩短页面加载的时间。JGEG格式图片我们推荐jpegtran,而PNG格式图片我们推荐工具OptiPNG。我们将jgp压缩后,再来观察一下相应快照。
通过与第一个快照对比,我们很容易地发现kubrickbg-ltr.jpg响应14206bytes缩小至2151bytes。还有二个图片文件响应也变小了不少。
精简请求包:清除不必要的Cookie
浏览器总会在每次发出响应的时候,将该domain有效的Cookie记录通过http头Cookie字段发给服务端。比如,传递session id等等。这就是说Cookie记录越多,请求包就越大,而一般的MTU大小是1500bytes,过大的httpd请求数据包会导致分包传输。对于静态资源请求而言,我们是不需要用到Cookie的,而只有动态内容请求才有可能用到Cookie。所以,我们可以通过主机别名、虚拟目录或虚拟主机的方式将静态资源分开。比如,这里我们使用http://static.perfgee.com来负责静态资源请求,而将动态内容资源请求通过http://www.perfgeeks.com作为请求url。httpd守护进程配置如下:
<VirtualHost 222.73.211.215:80> ServerAdmin liaowq.box@gmail.com DocumentRoot /var/www/vhosts/perfgeeks.com ServerName static.perfgeeks.com #ErrorLog /var/log/httpd/static.perfgeeks.com-error.log TransferLog /var/log/httpd/static.perfgeeks.com-access.log </VirtualHost>
同时,还要设置一下wordpress静态内容访问url,修改wp-conf.php,将以下内容加到/wp-conf.php
define (‘WPLANG’, ‘zh_CN’);
#define (‘COOKIE_DOMAIN’, ‘www.perfgeeks.com’);
define (‘WP_CONTENT_URL’, ‘http://static.perfgeeks.com/wp-content’);
相较于上一次的快照而言,除了第一个请求之外,其它的请求包大小略有减小。请求包的总体大小而言从5432bytes减小至4344bytes,也减小了1KB。对于大型网站而言,这一准则优化效果更让人喜悦。
持久连接:Keep-Alive
KeepAlive保持连接,它是一个什么概念呢?我们知道httpd的传输协议是TCP协议。TCP在传输之前,必须发送三个数据包建立连接,这也就是著名的TCP三次握手连接,同时结束传递的时候,需要发送四个数据包断开连接,这正是著名的TCP四次挥手。这意味着每个http请求与响应都要进行三次握手和四次挥手,一共来回7个数据包。7个http请求除http产生的网络数据包之外,还有49个数据包需要发送。Keep-Alive机制就是将第一个http请求建立的连接留给后面几次http请求、响应数据包使用,并且让第一次连接关闭延迟到最后一次http请求响应结束后才关闭。这样,我们所有请求就只要处理7个数据包,省下了42个数据包。Apache的KeepAlive启用配置如下:
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 10
这三个配置参数的意思,就是启用KeepAlive机制,自启用机制开始与该用户端建立的TCP连接供给10秒内数据包传输使用。也就是说,自第一个http请求建立了tcp连接后,10之内用户端发生的请求就不用像第一个请求一样,不需要去建立tcp连接了,而是借用第一个http请求建立的连接来传输http网络数据协议包,直到10秒以后,该连接才会关闭。我们的实例可以看出,该用户请求一次页面,共有7个http请求,花费时间大概在5之内。做为一个blog网站,一般用户会连接请求二个页面,即首页打开文章,并会在文章查看页面停留很长一段时间。也就是说,用户最有可能在10秒之内发送一堆请求,之后的很长一段时间不会再发送请求了。这一节,我们没有快照,有兴趣的朋友可以通过tcpdump查看整个过程。KeepAlive的缺点在保持连接的时候,连接会占用内存(socket也是一种文件类型)
ETag有必要吗?
自http1.1开始支持ETag功能,ETag其实就是Last-Modified补充机制,我们在这里就不再赘述了。Apache共通过inode + mtime产生一串字符串作为ETag发送给客户端。它是消耗性能的,也存在安全风险。同时,对于多服务器(web集群)而言不同服务器httpd守护进程产生的ETag是不一致的。所以,如果你使用ETag的时候,认真的询问自己是否真的有必要使用ETag!~如果你跟我们一样,觉得暂时没有必要,则通过以下配置关闭ETag(我们使用的是Apache)
FileETag none
最终,经过我们优化最终的快照如下图:
跟第一次的快照对比,优化的效果还是很明显地
1,请求数量,由最初的8个变成了现在的1个
2,发送包大小,由3400bytes缩小到现在的718bytes
3,响应包大小,由最初的146081bytes缩小至1025bytes
4,页面加载时间,由最初的10.296秒减小至现在的0.811秒
避免重定向
重定向虽然有的时候在跟踪流量,页面跳转等情况下随着业务需求难以避免,但是我们还是要尽量避免重定向的可能,它是很伤页面性能的。最常见的重定向是301和302。下面,我们访问了回复最多的一条贴子<<PHP session 浅析I>>。我们会发现多了很多条请求,还有重定向。这些请求发向gavatar.com主机,从avatar这个关键来看,我们可以确定该请求是用于下载回复者头像图片的请求。所以,我们把Wordpress回复头像功能禁用了,之后又恢复了太平。
禁用wordpress回复头像功能后的一个快照
我们对比禁用头像功能后的快照,页面下载速度明显比没有禁用头像功能的页面下载快很多。请求数量,请求数据包总大小,响应包总大小,页面下载时间都明显下降。
Web前端优化CheckList
除了上述web前端优化准则,还有一些准则我们这里并不”适用”。必须这是一个blog,应用并不复杂。这些准则在各用web应用都是非常有帮助的。所以,我们这里整理了一份Web前端优化CheckList
1)图片地图,减少请求数量
2)CSS Sprites,它总是能够减少请求数量
3)内联图片,小图标可以使用内联图片,减少请求数量
4)合并外置js或css脚本文件,减少请求数据
5)Expires,缓存响应到用户本地,减少请求数量
6)Gzip压缩,压缩text类型的http响应数据包,减小网络数据传递量。
7)代理缓存
8)CSS样式脚本放在页面的顶部,尽早加载
9)Javascript脚本放在页面的底部,延迟加载
10)并行下载,将不同的组件请求通过CNAME使用不同的URL。
11)减少DNS查询,缓存DNS查询
12)精简Javascript脚本和CSS脚本
13)避免没有意义的重定向
14)避免Javascript脚本重复调用
15)ETag?
16)保持连接Keep-Alive
17)外置Javascript和CSS
18)Cookieless请求,尽量少使用Cookie,把数据存放在服务端
19)精简http请求数据包。Query_String不要太长和Cookie记录不要过多
20)延迟加载Javascript
受篇幅影响,我们将不在这里详细介绍,你可以通过阅读Google Page Speed Document和High Performance Web Sites。