在连续写了两篇关于「HTTP/2 与 WEB 性能优化」的文章后,今天来写这个系列的***一篇。在正式开始之前,我们先来简单回顾下之前两篇文章:
「HTTP/2 与 WEB 性能优化(一)」的结论是:HTTP/2 的 Server Push 机制,可以让重要的 JS、CSS 等资源尽快加载,从而不再需要 HTTP/1 中「将重要资源内联在页面头部」的优化方案了。
「HTTP/2 与 WEB 性能优化(二)」的结论是:HTTP/2 支持了多路复用,HTTP 连接变得十分廉价,之前为了节省连接数所采用的类似于「资源合并、资源内联」等优化手段不再需要了。多路复用可以在一个 TCP 连接上建立大量 HTTP 连接,也就不存在 HTTP 连接数限制了,HTTP/1 中常见的「静态域名」优化策略不但用不上了,还会带来负面影响,需要去掉。另外,HTTP/2 的头部压缩功能也能大幅减少 HTTP 协议头部带来的开销。
但 HTTP/2 并不是***的,并不是说用了 HTTP/2 就不再需要性能优化了。我在本系统第二篇文章末尾写到:
据官方预测,HTTP/1 至少还需要 10 年才能彻底退出历史舞台,另外尽管 HTTP/2 协议允许脱离 TSL 部署,但 Chrome 和 Firefox 都表示不支持非 TLS 的 HTTP/2,之后很可能一个网站会同时提供 HTTP/1.1、HTTP/1.1 over TLS、HTTP/2 over TLS 三种服务。如何在每种情况下,都能给用户提供***的体验,需要更加深入的优化研究和更加精细的优化策略。
实际上,除了前两篇文章中提到的这些需要为 HTTP/2 做出调整的优化策略之外,其余大部分 HTTP/1 时期的优化策略依然有效。HTTP/1 的 WPO 并不是什么新鲜话题,大家早就熟门熟路了,本文只打算列举其中几个:
启用压缩
压缩的目的是让传输的数据变得更小。我们的线上代码(JS、CSS 和 HTML)都会做压缩,图片也会做压缩(PNGOUT、Pngcrush、JpegOptim、Gifsicle 等)。对于文本文件,在服务端发送响应之前进行 GZip 压缩也很重要,通常压缩后的文本大小会减小到原来的 1/4 - 1/3。对代码进行内容压缩已经有成熟的工具和标准流程了,而服务端的 GZip 更是标配,所以「压缩」是一项收益投入比很高的优化手段。
使用 HTTP 缓存
任何一个 WEB 项目,要提高性能,各个环节的缓存必不可少。利用好 HTTP 协议的缓存机制,可以大幅减少传输数据,减少请求,这又是一项收益投入比超高的优化手段。这里把之前我写的 HTTP/1.1 缓存机制介绍翻出来:
首先,服务端可以通过响应头里的 Last-Modified(***修改时间) 或者 ETag(内容特征) 标记实体。浏览器会存下这些标记,并在下次请求时带上 If-Modified-Since: 上次 Last-Modified 的内容 或 If-None-Match: 上次 ETag 的内容,询问服务端资源是否过期。如果服务端发现并没有过期,直接返回一个状态码为 304、正文为空的响应,告知浏览器使用本地缓存;如果资源有更新,服务端返回状态码 200、新的 Last-Modified、Etag 和正文。这个过程被称之为 HTTP 的协商缓存,通常也叫做弱缓存。
可以看到协商缓存并不会节省连接数,但是在缓存生效时,会大幅减小传输内容(304 响应没有正文,一般只有几百字节)。另外为什么有两个响应头都可以用来实现协商缓存呢?这是因为一开始用的 Last-Modified 有两个问题:1)只能精确到秒,1 秒内的多次变化反映不出来;2)在轮询的负载均衡算法中,如果各机器读到的文件修改时间不一致,有缓存无故失效和缓存不更新的风险。HTTP/1.1 并没有规定 ETag 的生成规则,而一般实现者都是对资源内容做摘要,能解决前面两个问题。
另外一种缓存机制是服务端通过响应头告诉浏览器,在什么时间之前(Expires)或在多长时间之内(Cache-Control: Max-age=xxx),不要再请求服务器了。这个机制我们通常称之为 HTTP 的强缓存。
一旦资源***强缓存规则后,再次访问完全没有 HTTP 请求(Chrome 开发者工具的 Network 面板依然会显示请求,但是会注明 from cache;Firefox 的 firebug 也类似,会注明 BFCache),这会大幅提升性能。所以我们一般会对 CSS、JS、图片等资源使用强缓存,而入口文件(HTML)一般使用协商缓存或不缓存,这样可以通过修改入口文件中对强缓存资源的引入 URL 来达到即时更新的目的。
这里也解释下为什么有了 Expire,还要有 Cache-Control。也有两个原因:1)Cache-Control 功能更强大,对缓存的控制能力更强;2)Cache-Control 采用的 max-age 是相对时间,不受服务端 / 客户端时间不对的影响。
另外关于浏览器的刷新(F5 / cmd + r)和强刷(Ctrl + F5 / shift + cmd +r):普通刷新会使用协商缓存,忽略强缓存;强刷会忽略浏览器所有缓存(并且请求头会携带 Cache-Control:no-cache 和 Pragma:no-cache,用来通知所有中间节点忽略缓存)。只有从地址栏或收藏夹输入网址、点击链接等情况下,浏览器才会使用强缓存。
减少 DNS 查询
我们知道,建立 TCP 连接需要知道目标 IP,而绝大部分时候给浏览器的是域名。浏览器需要先将域名解析为 IP,这个过程就是 DNS 查询,一般需要几毫秒到几百毫秒,移动环境下会更慢。DNS 解析完成之前,请求会被 Block。浏览器一般都会缓存 DNS 查询结果,页面使用的域名(包括子域名)越少,花费在 DNS 查询上的开销就越小。另外,合理使用浏览器的 DNS Prefetching 技术,也是很好的做法。
减少重定向
无论是通过服务端响应头产生的重定向,还是通过 或者 JS 产生的重定向,都可能引入新的 DNS 查询、新的 TCP 连接以及新的 HTTP 请求,所以减少重定向也很重要。浏览器基本都会缓存通过 301 Moved Permanently 指定的跳转,所以对于***性跳转,可以考虑使用状态码301。对于启用了 HTTPS 的网站,配置 HSTS 策略,也可以减少从 HTTP 到 HTTPS 的重定向。
WEB 性能优化是一个系统工程,不可能在这一篇文章里写完,我决定先就写到这儿。***,推荐一个 Chrome 扩展:HTTP/2 and SPDY indicator,它可以在地址栏显示当前网站是否启用了 SPDY 或者 HTTP/2,点击图标可以直接打开 Chrome 的 HTTP/2 的调试界面,十分方便。