You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 15 | 如何提升HTTP/1.1性能?
你好,我是陶辉。
上一讲介绍了为应用层信息安全保驾护航的TLS/SSL协议这一讲我们来看看最常用的应用层协议HTTP/1.1该如何优化。
由于门槛低、易监控、自表达等特点HTTP/1.1在互联网诞生之初就成为最广泛使用的应用层协议。然而它的性能却很差最为人诟病的是HTTP头部的传输占用了大量带宽。由于HTTP头部使用ASCII编码方式这造成它往往达到几KB而且滥用的Cookie头部进一步增大了体积。与此同时REST架构的无状态特性还要求每个请求都得重传HTTP头部这就使消息的有效信息比重难以提高。
你可能听说过诸如缓存、长连接、图片拼接、资源压缩等优化HTTP协议性能的方式这些优化方案有些从底层的传输层优化入手有些从用户使用浏览器的体验入手有些则从服务器资源的编码入手五花八门导致我们没有系统化地优化思路往往在性能提升上难尽全功。
那么如何全面地提升HTTP/1.1协议的性能呢我认为在不升级协议的情况下有3种优化思路首先是通过缓存避免发送HTTP请求其次如果不得不发起请求那么就得思考如何才能减少请求的个数最后则是减少服务器响应的体积。
接下来我们就沿着这3个思路看看具体的优化方法。
## 通过缓存避免发送HTTP请求
如果不走网络就能获得HTTP响应这样性能肯定最高。HTTP协议设计之初就考虑到了这一点缓存能够让客户端在免于发送HTTP请求的情况下获得服务器的资源。
缓存到底是如何做到的呢其实很简单它从时间维度上做文章把第1份请求及其响应保存在客户端的本地磁盘上其中请求的URL作为关键字部分HTTP头部也会加入关键字例如确定服务器域名的Host头部而响应就是值。这样后续发起相同的请求时就可以先在本地磁盘上通过关键字查找如果找到了就直接将缓存作为服务器响应使用。读取本地磁盘耗时不过几十毫秒这远比慢了上百倍且不稳定的网络请求快得多。
![](https://static001.geekbang.org/resource/image/9d/ab/9dea133d832d8b7ab642bb74b48502ab.png)
你可能会问服务器上的资源更新后客户端并不知道它若再使用过期的缓存就会出错这该如何解决因此服务器会在发送响应时预估一个过期时间并在响应头部中告诉客户端而客户端一旦发现缓存过期则重新发起网络请求。HTTP协议控制缓存过期的头部非常多而且通常这是在服务器端设置的我会在本专栏的第4部分“分布式系统优化”结合服务器操作再来介绍这里暂时略过。
当然过期的缓存也仍然可以提升性能如下图所示当客户端发现缓存过期后会取出缓存的摘要摘要是从第1次请求的响应中拿到的把它放在请求的Etag头部中再发给服务器。而服务器获取到请求后会将本地资源的摘要与请求中的Etag相比较如果不同那么缓存没有价值重新发送最新资源即可如果摘要与Etag相同那么仅返回不含有包体的304 Not Modified响应告知客户端缓存仍然有效即可这就省去传递可能高达千百兆的文件资源。
![](https://static001.geekbang.org/resource/image/a3/62/a394b7389d0cdb5f0866223681e19b62.png)
至于Etag摘要究竟是怎样生成的各类Web服务器的处理方式不尽相同比如Nginx会将文件大小和修改时间拼接为一个字符串作为文件摘要详见[《Nginx核心知识100讲》第97课](https://time.geekbang.org/course/detail/138-76358))。过期缓存在分布式系统中可以有效提升可用性,\[第25课\] 还会站在反向代理的角度介绍过期缓存的用法。
浏览器上的缓存只能为一个用户使用故称为私有缓存。代理服务器上的缓存可以被许多用户使用所以称为共享缓存。可见共享缓存带来的性能收益被庞大的客户端群体放大了。你可以看到在下面的REST架构图中表示缓存的$符号缓存的英文名称是cache由于它的发音与cash很像所以许多英文文档中用美元符号来表示缓存既存在于User Agent浏览器中也存在于Proxy正向代理服务器和Gateway反向代理上。
![](https://static001.geekbang.org/resource/image/b6/ff/b6950aa62483c56438c41bc4b2f43bff.png "图片来自网络")
可见缓存与互联网世界的网络效率密切相关用好缓存是提升HTTP性能最重要的手段。
## 如何降低HTTP请求的次数
如果不得不发起请求就应该尽量减少HTTP请求的次数这可以从减少重定向次数、合并请求、延迟发送请求等3个方面入手。
首先来看看什么是重定向请求一个资源由于迁移、维护等原因从url1移至url2后在客户端访问原先的url1时服务器不能简单粗暴地返回错误而是通过302响应码及Location头部告诉客户端资源已经改到url2了而客户端再用url2发起新的HTTP请求。
可见重定向增加了请求的数量。尤其客户端在公网中时由于公网速度慢、成本高、路径长、不稳定而且为了信息安全性还要用TLS协议加密这些都降低了网络性能。从上面的REST架构图可以看到HTTP请求会经过多个代理服务器如果将重定向工作交由代理服务器完成就能减少网络消耗如下图所示
![](https://static001.geekbang.org/resource/image/0d/2a/0d1701737956ce65f3d5ec8fa8009f2a.png)
更进一步客户端还可以缓存重定向响应。RFC规范定义了5个重定向响应码如下表所示其中客户端接收到301和308后都可以将重定向响应缓存至本地之后客户端会自动用url2替代url1访问网络资源。
![](https://static001.geekbang.org/resource/image/85/70/85b55f50434fc1acd2ead603e5c57870.jpg)
其次我们来看如何合并请求。当多个访问小文件的请求被合并为一个访问大文件的请求时这样虽然传输的总资源体积未变但减少请求就意味着减少了重复发送的HTTP头部同时也减少了TCP连接的数量因而省去了TCP握手和慢启动过程消耗的时间参见第12课。我们具体看几种合并请求的方式。
一些WEB页面往往含有几十、上百个小图片用[CSS Image Sprites技术](https://www.tutorialrepublic.com/css-tutorial/css-sprites.php)可以将它们合成一张大图片而浏览器获得后可以根据CSS数据把它切割还原为多张小图片这可以大幅减少网络消耗。
[![](https://static001.geekbang.org/resource/image/e4/ed/e4a760a95542701d99b8a38ccddaebed.png "图片来自[黑染枫林的CSDN博客]")](https://blog.csdn.net/weixin_38055381/article/details/81504716)
类似地在服务器端用webpack等打包工具将Javascript、CSS等资源合并为大文件也能起到同样的效果。
除此以外还可以将多媒体资源用base64编码后以URL的方式嵌入HTML文件中以减少小请求的个数参见[RFC2397](https://tools.ietf.org/html/rfc2397))。
![](https://static001.geekbang.org/resource/image/27/fb/27aa119e0104f41a6718987cdf71c8fb.png)
用Chrome浏览器开发者工具的Network面板可以轻松地判断各站点是否使用了这种技术。关于Network面板的用法我们在此就不赘述了可参考[《Web协议详解与抓包实战》第9课](https://time.geekbang.org/course/detail/175-93594),那里有详细的介绍)。
![](https://static001.geekbang.org/resource/image/ab/74/ab75920b0c43e4f7fcbc5933005afb74.png)
当然这种合并请求的方式也会带来一个新问题当其中一个资源发生变化后客户端必须重新下载完整的大文件这显然会带来额外的网络消耗。在落后的HTTP/1.1协议中合并请求还算一个不错的解决方案在下一讲将介绍的HTTP/2出现后这种技术就没有用武之地了。
最后我们还可以从浏览页面的体验角度上减少HTTP请求的次数。比如有些HTML页面上引用的资源其实在当前页面上用不上而是供后续页面使用的这就可以使用懒加载[lazy loading](https://zh.wikipedia.org/wiki/%E6%83%B0%E6%80%A7%E8%BC%89%E5%85%A5) 技术延迟发起请求。
当不得不发起请求时,还可以从服务器的角度通过减少响应包体的体积来提升性能。
## 如何重新编码以减少响应的大小?
减少资源体积的唯一方式是对资源重新编码压缩,其中又分为[无损压缩](https://zh.wikipedia.org/wiki/%E6%97%A0%E6%8D%9F%E6%95%B0%E6%8D%AE%E5%8E%8B%E7%BC%A9)与[有损压缩](https://zh.wikipedia.org/wiki/%E6%9C%89%E6%8D%9F%E6%95%B0%E6%8D%AE%E5%8E%8B%E7%BC%A9)两种。
先来看无损压缩,这是指压缩后不会损失任何信息,可以完全恢复到压缩前的原样。因此,文本文件、二进制可执行文件都会使用这类压缩方法。
源代码也是文本文件,但它有自身的语法规则,所以可以依据语法先做一轮压缩。比如[jQuery](https://jquery.com/download/) 是用javascript语言编写的库而标准版的jQuery.js为了帮助程序员阅读含有许多空格、回车等符号但机器解释执行时并不需要这些符号。因此根据语法规则将这些多余的符号去除掉就可以将jQuery文件的体积压缩到原先的三分之一。
![](https://static001.geekbang.org/resource/image/de/ba/de0fc2a64a100bc83fc48eb2606fedba.png)
接着可以基于信息熵原理进行通用的无损压缩这需要对原文建立统计模型将出现频率高的数据用较短的二进制比特序列表示而将出现频率低的数据用较长的比特序列表示。我们最常见的Huffman算法就是一种执行速度较快的实践在下一讲的HTTP/2协议中还会用到它。在上图中可以看到最小版的jQuery文件经过Huffman等算法压缩后体积还会再缩小三分之二。
支持无损压缩算法的客户端会在请求中通过Accept-Encoding头部明确地告诉服务器
```
Accept-Encoding: gzip, deflate, br
```
而服务器也会在响应的头部中告诉客户端包体中的资源使用了何种压缩算法Nginx开启资源压缩的方式参见《Nginx核心知识100讲》[第131课](https://time.geekbang.org/course/detail/138-79618)和[第134课](https://time.geekbang.org/course/detail/138-79621)
```
content-encoding: gzip
```
虽然目前gzip使用最为广泛但它的压缩效率以及执行速度其实都很一般Google于2015年推出的[Brotli](https://zh.wikipedia.org/wiki/Brotli) 算法在这两方面表现都更优秀也就是上文中的br其对比数据如下
[![](https://static001.geekbang.org/resource/image/62/cb/62e01433ad8ef23ab698e7c47b7cc8cb.png "图片来源:[https://quixdb.github.io/squash-benchmark/]")](https://quixdb.github.io/squash-benchmark/)
再来看有损压缩它通过牺牲质量来提高压缩比主要针对的是图片和音视频。HTTP请求可以通过Accept头部中的q质量因子参见[RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.2)),告诉服务器期望的资源质量:
```
Accept: audio/*; q=0.2, audio/basic
```
先来看图片的压缩。目前压缩比较高的开源算法是Google在2010年推出的[WebP格式](https://zh.wikipedia.org/wiki/WebP),你可以在[这个页面](https://isparta.github.io/compare-webp/index.html#12345)看到它与png格式图片的对比图。对于大量使用图片的网站使用它代替传统格式会有显著的性能提升。
动态的音视频压缩比要比表态的图片高很多!由于音视频数据有时序关系,且时间连续的帧之间变化很小,因此可以在静态的关键帧之后,使用增量数据表达后续的帧,因此在质量略有损失的情况下,音频体积可以压缩到原先的几十分之一,视频体积则可以压缩到几百分之一,比图片的压缩比要高得多。因此,对音视频做有损压缩,能够大幅提升网络传输的性能。
对响应资源做压缩不只用于HTTP/1.1协议事实上它对任何信息传输场景都有效消耗一些CPU计算力在事前或者事中做压缩通常会给性能带来不错的提升。
## 小结
这一讲我们从三个方面介绍了HTTP/1.1协议的优化策略。
首先客户端缓存响应可以在有效期内避免发起HTTP请求。即使缓存过期后如果服务器端资源未改变仍然可以通过304响应避免发送包体资源。浏览器上的私有缓存、服务器上的共享缓存都对HTTP协议的性能提升有很大意义。
其次是降低请求的数量如将原本由客户端处理的重定向请求移至代理服务器处理可以减少重定向请求的数量。或者从体验角度使用懒加载技术延迟加载部分资源也可以减少请求数量。再比如将多个文件合并后再传输能够少传许多HTTP头部而且减少TCP连接数量后也省去握手和慢启动的消耗。当然合并文件的副作用是小文件的更新会导致整个合并后的大文件重传。
最后可以通过压缩响应来降低传输的字节数选择更优秀的压缩算法能够有效降低传输量比如用Brotli无损压缩算法替换gzip或者用WebP格式替换png等格式图片等。
但其实在HTTP/1.1协议上做优化效果总是有限的下一讲我们还将介绍在URL、头部等高层语法上向前兼容的HTTP/2协议它在性能上有大幅度提升是如gRPC等应用层协议的基础。
## 思考题
除了我今天介绍的方法以外使用KeepAlive长连接替换短连接也能提升性能你还知道有哪些提升HTTP/1.1性能的方法吗?欢迎你在留言区与我一起探讨。
感谢阅读,如果你觉得这节课对你有一些启发,也欢迎把它分享给你的朋友。