Introduction to Website Performance
2018-02-28
减少HTTP请求
图片地图
- 通过 HTML
map
标签实现将用户的点击映射到一个操作。1
2
3
4
5
6
7
8<img usemap='#map1' border=0 src='images/imagemap.gif?t=1196816255'>
<map name='map1'>
<area shape='rect' coords='0,0,31,31' href='home.html'title="Home">
<area shape='rect' coords='36,0,66,31' href='gifts.html' title="Gifts">
<area shape="rect" coords="71,0,101,31" href="cart" title="Cart">
<area shape="rect" coords="106,0,136,31" href="settings.html" title="Settings">
<aare shape="rect" coords="141,0,171,31" href="help.html" title="Help">
</map>
- 通过 HTML
CSS Sprites
- 使用一个DIV包含五个链接,将所有图片合并成一个图片作为DIV的背景图片,五个链接分别用SPAN表示,每个具有不同的类,通过
background-position
属性指定CSS Sprites的偏移量。
- 使用一个DIV包含五个链接,将所有图片合并成一个图片作为DIV的背景图片,五个链接分别用SPAN表示,每个具有不同的类,通过
- 内联图片
- 使用data:URL模式可以在Web页面中包含图片但无需任何额外的HTTP请求。
- 合并脚本和样式表
- 通过Webpack将脚本打包成一个统一脚本
- 网站由于可能存在多个页面,每个页面可能要求使用不同脚本,由此可能不同的脚本组合会有不同的打包方式, 打包方式会逐渐变复杂。 可以通过Webpack打包形成按需加载。
使用内容发布网络(CDN)
- 什么是内容发布网络(CDN)?
- 内容发布网络是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。
- 无论如何也不要使用HTTP重定向来将用户指向指定服务器。
- 依赖CDN的一个缺点是你的响应时间可能会受到其他网站的流量影响。
- 另一个缺点是无法直接控制组件服务器所带来的麻烦。
- 如果CDN服务器的性能下降,你网站的性能也会下降。
- CDN用于发布静态内容——图片、脚本、样式表、Flash等, 提供动态HTML页面会引入特殊的存储需求。
- 减少网站响应时间
添加Expires头
- Expires头
- Web服务器使用Expires头来告诉Web客户端它可以使用一个组件的当前副本直到指定的时间为止。
- 长久的Expires头常用于图片,但应该将所有的组件上,包括脚本、样式表、Flash。
- 缺点: 要求服务器和客户端的时钟严格同步。
- 缺点: 过期时间需要经常检查,一旦未来这一天到来,还需要在服务器配置中提供一个新的日期。
Max-Age 和 mod_expires
- Cache-Control使用max-age指令指定组件被缓存多久,它以秒为单位。如果从组件被请求开始过去的秒数少于max-age,浏览器就使用缓存版本。
- max-age和Expires两个响应头同时出现时,HTTP规范规定max-age指令将重写Expires头。
- mod_expires Apache模块使你在使用Expires头时能够像使用max-age那样以相对的方式设置日期。这通过Expires-Default指令完成。
- 跨浏览器改善缓存的最佳解决方案就是使用由ExpiresDefault设置的Expires头。
空缓存 VS 完整缓存
- 空缓存:如果你的页面中的则缓存为“空”。
- 完整缓存:如果你的页面中的可缓存组件都在缓存中,则为完整缓存。
- 空缓存或完整缓存页面浏览的数量取决于Web应用程序的本质。
- 不仅仅是图片
- HTML文档不应该使用长久的Expires头,因为它包含动态内容, 这些内容在每次用户请求时都被更新。
- 我们可以通过监控网站组件的
Last-modified
属性来统计哪些组件的更新频率比较低,可以为更新频率低的组件设置Expire头。
- 修订文件名
- 如果我们将组件配置为可以由浏览器代理缓存, 当这些组件改变时用户如何获得更新呢?
- 当出现了Expires头时,直到过期日期为止一直会使用缓存的版本,浏览器不会检查任何更新,直到过了过期日期。 这也是为什么使用Expires头能够显著减少响应时间,浏览器直接从硬盘上读取组件而无需产生任何HTTP流量。因此即使在服务器上更新了组件,已经访问过的用户也不大可能获取最新的组件。
- 最有效的解决方案是修改其所有链接, 全新的请求将从原始服务器下载最新内容,对于PHP和Perl等动态语言生成HTML页面,简单的解决方案是为所有组件的文件名使用变量(将版本号或者修改日期作为文件名的一部分)。
压缩组件(通过减少HTTP响应大小来减少响应时间)
- 压缩是如何工作的?
- 自HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持。
- Web服务器通过响应中的Content-Encoding头部来通知Web客户端。
- gzip是目前最理想的压缩方式。
1
Accept-Encoding: gzip deflate
1 | Content-Encoding: gzip |
- 压缩什么
- 很多网站压缩HTML文档,压缩脚本和样式表也非常常见。
- 压缩需要耗费额外的CPU周期来完成压缩,客户端要对压缩文件进行解压。
- 通常大于1KB或者2KB的文件进行压缩。
mod_gzip_minimum_file_size
指令控制着希望压缩文件的最小值,默认值是500B。
- 节省
- 压缩通常能将响应的数据量减少将近70%。
配置
- Apache 1.3 ——mod_gzip
- Apache 1.3的gzip压缩由mod_gzip模块提供
mod_gzip_on
启动mod_gzipmod_gzip_item_include
、mod_gzip_item_exclude
基于文件类型、MIME类型、用户代理等定义哪些需要压缩,哪些不需要压缩。- 最需要的修改就是在配置中明确指出需要明确压缩脚本和样式表。
mod_gzip_can_negotiate
和mod_gzip_update_static
指令可以将保存压缩过的内容自动保存在磁盘上, 并在原内容发生变化时更新压缩过的内容。
- Apache 2.x ——mod_deflate
- 压缩通过mod_deflate完成,但也是gzip压缩。
- Apache 1.3 ——mod_gzip
代理缓存
- 当浏览器通过代理来发送请求时,假设针对某个URL发送到代理的第一个请求来自一个不支持gzip的浏览器,这是到达代理的第一个请求,因此其缓存为空,代理会将该请求发送给服务器。此时服务器的响应是未经过压缩的。这个没有压缩的响应被代理缓存起来并发给浏览器。现在,假设到达代理的第二个请求是同一个URL, 来自一个支持gzip的浏览器,代理会使用其缓存中未经压缩的内容进行响应。失去了压缩的机会。
- 可以在Web服务器的响应中添加
Vary
头,这使得代理缓存响应的多个版本,为Accept-Encoding 请求头的每个值缓存一份。
- 边缘情况
- 老版本浏览器不支持gzip压缩, 可以使用
mod_gzip_item_include reqheader "User-Agent: MISE[6-9]
来指定浏览器白名单。 - 当与代理缓存结合考虑时情况变得更加复杂。最好使用
Cache-Control: private
来禁止所有浏览器使用代理缓存。
- 老版本浏览器不支持gzip压缩, 可以使用
将样式表放在顶部
- 逐步呈现
- 将样式表放在文档底部会导致在浏览器中阻止内容逐步呈现。为避免当样式变化时重绘页面中的元素,浏览器会阻塞内容逐步呈现。由此会产生白屏现象。
sleep.cgi
白屏
- 将样式表放在文档底部延迟页面加载的问题只发生在IE浏览器中。当发生这种现象时页面完全空白,直到页面所有内容同时涌上屏幕。
- 将样式表放在顶部HEAD中解决白屏问题,引入样式表的方式有两种:使用LINK标签或者@import规则。
- @import规则引入样式表会导致组件下载的无序性,即使在顶部也可能会产生白屏问题。
无样式内容的闪烁
- 如果样式表仍在加载,构建呈现树就是一种浪费,因为在所有样式表加载并解析完毕之前无需绘制任何东西,否则,在其准备好之前显示的内容会重新呈现, 造成闪烁( FOUC Flash of Unstyled Content)。
- IE在新窗口中加载页面、重新加载和作为主页时,会发生白屏。单击链接、使用书签或者键入URL时,IE选择FOUC, Firfox则总是选择FOUC
- 为避免白屏和FOUC请总是在Head标签中使用LINK标签引入样式表。
将脚本放在底部
- 将脚本放在中、上部带来的问题:
- 所有位于脚本下方的组件只有在脚本完成下载之后才会开始下载。
- 所有位于脚本下方的组件只有在脚本完成下载之后才会开始渲染。
- 并行下载
- HTTP1.1规范建议每个主机名并行地下载两个组件。、
- IE 和 Firefox都遵循这一建议。
- 脚本阻塞下载
- 下载脚本时,并行下载被禁用,即使使用了不同的主机名,其中一个原因是,脚本中可能使用
document.write
来修改页面内容,因此浏览器会等待,以确保页面能够恰当地布局。 - 阻塞并行下载,也是防止并行下载脚本导致脚本执行的顺序错误。
- 脚本设置DEFER属性表明脚本不包含
document.write
则不会阻碍并行下载。(不包含Firefox)
- 下载脚本时,并行下载被禁用,即使使用了不同的主机名,其中一个原因是,脚本中可能使用
- 脚本放在顶部导致页面内容下载被阻塞造成白屏现象。
避免CSS表达式
- CSS表达式只在IE中工作。
- CSS表达式是动态设置CSS属性的一种方式。
background-color: expression( (new Date()).getHour() % 2 ? "#B8D4FF" : "#F08A00");
动态设置背景颜色,设置为每小时更新一次。- Expression 方法接受一个JavaScript表达式,CSS属性被设置为对JavaScript表达式求值的结果。
对CSS表达式的频繁求值(在页面呈现,大小改变,页面滚动,鼠标移动)导致性能低下。
一次性表达式
- 使用CSS表达式必须求值一次, 可以在这一次执行中重写其本身。
- 事件处理器
- 通过设置事件处理器来设定CSS属性值。
使用外部JavaScript和CSS
- 页面查看
- 每个用户产生的页面查看越小, 内联JavaScript和CSS的论据越强势。
- 空缓存 VS 完整缓存
- 组件重用
- 每个用户每月会话数量较高,普通用户在一个会话中访问多个不同页面的网站: 适合联合所有JavaScript和CSS到一个文件中。
- 对于普通用户只访问一个页面和很少的跨页访问的网站来说:为每个页面单独提供一个分离的外部组件。
- 折中的方法就是将你的页面分成几种页面类型,然后为每种页面类型创建单独的脚本和样式表。
- 典型的对比结果
两全其美
- 加载后下载——通过onload事件实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<script>
function doOnload(){
setTimeout("downloadComponents()", 1000);
}
window.onload = doOnload;
function downloadComponents(){
downloadJS("http://stevesouders.com/examples/testsma.js");
downloadCSS("http://stevesouders.com/testsm.css");
}
function downloadJS(url){
var elem = document.createElement("script");
elem.src = url;
document.body.appendChild(elem);
}
function downloadCSS(url){
var elem = document.createElement("link");
elem.rel = "stylesheet";
elem.type = "text/css";
elem.href = url;
document.body.appendChild(elem);
}
</script>
- 加载后下载——通过onload事件实现
动态内联
- 服务器可以用cookies做指示,如果cookie不存在,就采用内联JavaScript和CSS,如果cookie出现了,则可能外部组件位于浏览器的缓存中,并使用了外部文件。
- 由于每个用户开始的时候都没有cookie,因此必须有一种途径来引导这一过程。这可以通过使用前一个例子中的加载后下载技术来完成。