系统性能优化-9 HTTP1.1
由于 HTTP 头部使用 ASCII 编码方式,这造成它往往达到几 KB,而且滥用的 Cookie 头部进一步增大了体积。与此同时,REST 架构的无状态特性还要求每个请求都得重传 HTTP 头部,这就使消息的有效信息比重难以提高。
在不升级协议的情况下,有 3 种优化思路:首先是通过缓存避免发送 HTTP 请求;其次,如果不得不发起请求,那么就得思考如何才能减少请求的个数;最后则是减少服务器响应的体积。
通过缓存避免发送 HTTP 请求
客户端缓存:浏览器把收到的请求和响应保存在磁盘上,关键字为 (url + 部分请求头部),这样后续发生请求时,就可以先从本地磁盘上找,查询磁盘的几十毫秒相对于慢了上百倍且不稳定的网络请求要好的多。不过当服务器的资源更新后,如何让客户端感知呢?服务端在首次响应时返回一个预计过期时间
和消息摘要
,当客户端发现已经过期了就会重新发送请求。
此外,过期缓存也是可以提升性能的,客户端发现缓存过期后,取出首次响应返回的摘要,放在 Etag 头部中,服务器获取到请求后,会将本地资源的摘要与请求中的 Etag 相比较,如果不同,那么缓存没有价值,重新发送最新资源即可;如果摘要与 Etag 相同,那么仅返回不含有包体的 304 Not Modified 响应,告知客户端缓存仍然有效即可,这就省去传递可能高达千百兆的文件资源。
Etag 摘要的生成与服务器的实现有关~
浏览器这样的缓存只能为当前用户使用,是私有缓存,如果能缓存在代理服务器上,就能作为共享缓存被多用户使用
降低 HTTP 请求次数
例如,当请求返回 302 重定向时,如果由客户端来再次请求,请求又要经过不稳定的外部网络,但如果是代理服务器来转发,就可以减少请求的路径,并且如果这个重定向被代理服务器所记忆,就能进一步减少网络消耗。
降低 HTTP 请求次数还可以通过合并请求的方式实现,例如一个页面的多个小图片利用 css image sprites 技术合成一张大图片,浏览器收到后根据 css 数据进行切割还原,类似地,在服务器端用 webpack 等打包工具将 Javascript、CSS 等资源合并为大文件,也能起到同样的效果。优点是能减少 tcp 的连接次数,同时减少请求次数也意为着 header 的传递次数也会减少
,当然,这种合并请求的方式也会带来一个新问题,即,当其中一个资源发生变化后,客户端必须重新下载完整的大文件
,这显然会带来额外的网络消耗。HTTP/2 就不需要这个技术了。
重新编码减少响应的大小
减少响应体积其实就是对资源进行压缩,用 cpu 换取网络带宽,这又分为无损压缩和有损压缩
支持无损压缩算法的客户端会在请求中通过 Accept-Encoding 头部明确地告诉服务器:
Accept-Encoding: gzip, deflate, br
服务器也会在响应中告诉客户端使用的哪种压缩算法:
content-encoding: gzip
虽然目前 gzip 使用最为广泛,但它的压缩效率以及执行速度其实都很一般,Google 于 2015 年推出的 Brotli 算法在这两方面表现都更优秀(也就是上文中的 br)
有损压缩通过牺牲质量来提高压缩比,主要针对的是图片和音视频。HTTP 请求可以通过 Accept 头部中的 q 质量因子,告诉服务器期望的资源质量
Accept: audio/*; q=0.2, audio/basic
例如图片的 wbep 格式就比 png 要更小
小结
如果一定要使用 HTTP 1.1,可以从三方面入手性能优化:
- 缓存
- 把响应缓存在本机磁盘或代理服务器,在代理服务器的缓存称为共享缓存,可以为多个用户使用。缓存离客户端越近返回越快,过期缓存也可以通过摘要值发挥作用
- 减少请求次数
- 代理服务器代替客户端进行重定向等
- 对小资源进行合并返回
- 对资源进行压缩返回
- 例如 js 文件就有源文件,min.js,min.zip,压缩算法 gzip、br 等
- 对图片,音视频进行压缩,某些资源可以使用 Base64 编码后嵌入 HTML 文件(HTML 文档本身就包含了多媒体资源的完整数据,浏览器可以直接解析并展示这些内容,而无需通过额外的 HTTP 请求获取这些资源)