《高性能网站建议指南》读书笔记

HTTP概述

C/S协议,请求与响应构成,短连接协议,请求响应完成后马上断开连接。
GET请求包含一个URL,然后是请求头,POST除了URL和请求头外,还拥有请求体,请求之前要组装请求体,所以速度上没有GET请求快。
HTTP响应包含请求码、响应头和响应体。

压缩

如果浏览器支持和服务器支持的话,使用压缩来减少响应大小。浏览器使用Accept-Encoding:gzip,deflate来声明它支持压缩。服务器使用Content-Encoding:gzip来确认响应内容已被压缩。

条件GET请求

对缓存的内容进行确认的请求(通过判断Last-Modified),如果服务端确认有效,则进行下一步确认是否使用缓存。

Expires

在次确认是否使用缓存,客户端和服务端的再一次往返确认,以执行有效性检验。

Keep-Alive

使浏览器和客户端可以在一次连接下进行多次请求,使用Connecttion头来支出对Keep-Alive的支持,使用Connecttion: close来关闭连接。

减少HTTP请求

80%~90%时间花在HTML文档所应用的组件上(图片、脚本、样式等)进行的HTTP请求上,因为是短连接,每次都需要进行重连,所以耗时长。

图片地图

CSS Sprites

合并图片,把css的背景图片合并在一张图片中,通过background-position来控制显示。

内联图片

通过是使用data:URL模式,如使用base64流图片,这样的图片无需进行HTTP请求。

合并脚本和样式表

每个脚本与样式表都要建立一次HTTP请求,与合并图片类似,把脚本和样式表合并在一起,减少HTTP请求次数。

使用内容发布网络(CDN)

内容发布网络

一组分布在多个不同地理位置的WEB服务器,用于更加有效的向用户发布内容。CDN可以选择地理位置最近的或者响应时间最短的服务器对其内容进行响应。

过程原理

  1. 请求域名
  2. 浏览器对域名进行理解
  3. CDN域名服务器返回指定域名的CNAME
  4. 浏览器对CNAME域名进行解析,得到CDN缓存服务器的IP地址
  5. 根据CDN缓存服务器的IP地址再一次进行请求
  6. 缓存服务器通过Cache内部专用DNS解析得到实际IP,并向该IP提交资源请求
  7. 缓存服务器得到资源后进行本地保存和返回给浏览器客户端。

添加Expires头

通过使用长久的Expires头,可以使一些资源被缓存,这会会在后续的页面浏览中避免不必要的HTTP请求。

Expires头

Web服务器用Expires头来告诉客户端,可以使用一个组件的当前副本,知道指定时间为止。
如Expires: Thu 15 Apr 2010 20:00:00 GMT
这是一个长久有效的Expires头,告诉浏览器该响应有效性持续到2010年4约14日为止,如果为一个页面中的一张图片返回了这个头,浏览器在后续的页面浏览中会使用缓存的图片,这将减少一个HTTP请求的数量。

Max-Age

Expires用的是一个特定的时间,它要求服务器和客户端的时钟严格同步,Cache-Control:Max-Age可以克服这种限制,Cache-Control使用max-age指令指定组件被缓存多久,它以秒为单位定义了一个更新窗。

压缩组件

如果HTTP请求产生的响应包很小,传输时间就会减少,因为只需要将很小的包从服务器传输到客户端,这样对速度较慢的带宽效果尤其明显。

压缩是如何工作的

从http1.1开始,Web客户端可以通过HTTP请求头中的Accept-Encoding来表示对压缩的支持。gzip是目前最流行和最有效的方法,deflate效果略逊且不太流行。

压缩什么

可以压缩HTML、JSON、XML、脚本和样式(很多网站没有做)。
图片和PDF不应该压缩,因为它们本来就被压缩了。

压缩成本:服务器CPU资源、客户端要对压缩的文件进行解压。

代理缓存

对代理服务器来说,可以缓存来自web服务器的资源,但是不同的浏览器对gzip的支持不一样,那么假如代理服务器缓存了一份gzip过的组件,但是一个不支持gzip的浏览器请求了这个代理服务器,那么将无法正常解压这个组件,所以需要在空缓存,也就是第一次请求是把给请求头加上Vary:Accept-Encoding,告诉代理服务器,为Accept-Encoding请求头的每个值多缓存一份,这样有无Accept-Encoding:gzip都可以进行处理了。

将样式表放在顶部

样式表与脚本的放置顺序决定了下载的优先级,由于CSS会阻塞DOM的渲染,所以CSS放在顶部,可以使页面第一次渲染的时候把样式呈现完整,如果CSS放在底部,页面会先呈现没有样式的页面,等CSS样式加载完成,在进行一次回流重绘,降低了用户体验。

将脚本放在底部

浏览器为了保证脚本的执行顺序,在解析html的时候,遇到JavaScript脚本标签,会阻止浏览器的并行加载。假如脚本放在页面顶部的话,会阻塞对其他内容的下载和呈现,直至脚本加载完毕,所以需要把脚本放在页面底部,这样不会阻止页面内容的呈现,而且页面中的可视组件可以今早下载。

避免CSS表达式

CSS表达式,即在CSS中是用JavaScript表达式,如下

1
background-color: expression((new Date()).getHours() % 2 ?  "#b8d4ff" : "#f08a00")

更新表达式

表达式的问题在于对其进行求值的频率比人们期望的要高,它不只在页面呈现的大小改变时求值,当页面滚动、甚至用户鼠标在页面上移动时都要求值。也正因为更新频率太高,所以会验证影响页面的性能。

一次性表达式

使用代码处理,让更新频率只执行一次,如下

1
background-color: expression(altBgcolor(this))
1
2
3
function altBgcolor(elem) {
Elem.style.backgroundColor = (new Date()).getHours() % 2 ? "#b8d4ff" : "#f08a00"
}

使用外部JavaScript和CSS

内联的优点:

  • 可以减少http请求

缺点:

  • 缺点为无法使用浏览器缓存
  • 无法并行下载。

外部JavaScript与CSS的优点:

  • 可以利用浏览器的缓存,加快完整缓存,但是对空缓存无效。
  • 对文件可以并行下载。
  • 可以使用cdn。

缺点:

  • ​增加http请求。

纯粹而言(空缓存),内联的效率要比外部文件要快得多,因为减少了很多http请求,但是当用户多次访问的时候(完整缓存),内联的效率会明显低于外部文件。

减少DNS查询

DNS:Domain Name System,将域名映射到IP地址,URL和实际宿主它们的服务器之间的间接层。通常,浏览器查找一个给定的主机名的IP地址要话费20~120毫秒,故DNS查询次数越多,页面效率越慢。

DNS缓存与TTL

DNS查找可以被缓存起来以提高性能,浏览器、操作系统都可以对DNS进行缓存,当我们浏览器缓存DNS后,就不会麻烦操作系统来解析这个域名。

TTL(Time-to-live),影响DNS缓存的因素,该值告诉客户端可以对该记录缓存多久,浏览器通常会忽略该值,操作系统才会考虑TTL。HTTP协议中的keep-alive可以同时覆盖TTL和浏览器的时间限制,也就是说只要浏览器与服务器保持这TCP连接的打开状态,就不会进行DNS查询。

减少DNS查找

当客户端的DNS缓存为空时候,DNS的查找数量与web页面中唯一主机名的数量相同,包括URL、图片、脚本、样式表等主机名。减少唯一主机名的数量就可以减少DNS查找数量(每个主机名并行下载的组件有限)。

精简JavaScript

通过工具构造静态资源文件,减少JavaScript与CSS文件大小来加快加载速度。

避免重定向

重定向会增加http请求的次数,会影响到整个网站的性能,但是必要的重定向又可以提高用户体验,所以我们需要在性能和用户体验之间去权衡,达到最好的目的。

以百度为例:用户在浏览器中输入网址 http://www.baidu.com/ 或者 http://www.baidu.com/index.php,实际上访问的都是本站的首页;用户在浏览器中输入网址 http://www.baidu.com/ 或者 http://baidu.com/,访问的依然都是本站的首页。

移除重复脚本

配置ETag

实体标签(Entity Tag)是Web服务器和浏览器用于确认缓存组件的有效性的一种机制。ETag是唯一表示了一个组件的一个特定版本的字符串。唯一的格式约束是该字符串必须用引号引起来。ETag的假如为验证实体提供了比最新更新时间更灵活的机制,即如果ETag不一致,则使用不使用缓存。