HTTP 是我们知识地图里面必不可少的一部分,也是面试必问知识点。HTTP2 号称可以让我们的应用更快、更简单、更稳定,它完美解决了 1.1 版本的诸多问题,本文和大家一起聊聊 HTTP2 的改进点。
正式讲 HTTP2 之前我们先讲一下HTTP的发展史。

现在我们先不聊 HTTP2,看一下 HTTP 发展到 1.1 存在有哪些问题:
先来一个 demo 感受一下吊炸天的 HTTP/2.0,这个 demo 是加载 379 张图片,来对比 HTTP/1.1 和 HTTP/2.0 的性能。

理论上 HTTP/2.0 会比 HTTP/1.1 有一倍多的性能提升,弱网环境下,性能提升会更加明显。 下面两张图是我在设置网络在 fast 3G 和 slow 3G 的性能对比。


是不是被HTTP/2.0的速度亮瞎了双眼?2333,接下来我们正式开始聊聊2.0。看看2.0 相比与1.1的一些重大改进。
HTTP2性能提升的核心就在于二进制分帧层。HTTP2是二进制协议,他采用二进制格式传输数据而不是1.x的文本格式。

很清晰的表达了HTTP/1.1的响应和2.0的区别。1.1响应是文本格式,而2.0把响应划分成了两个帧,图中的HEADERS(首部)和DATA(消息负载) 是帧的类型。了解更多帧的类型也就是说一条HTTP响应,划分成了两个帧来传输,并且采用二进制来编码。
这里我们来提三个概念。
上面提到HTTP/1.1的线头阻塞和多个TCP连接的问题,HTTP2的多路复用完美解决。HTTP2让所有的通信都在一个TCP连接上完成,真正实现了请求的并发。我们来看一下HTTP2具体是怎么实现的:

HTTP2建立一个TCP连接,一个连接上面可以有任意多个流(stream),消息分割成一个或多个帧在流里面传输。帧传输过去以后,再进行重组,形成一个完整的请求或响应。这使得所有的请求或响应都无法阻塞。 我们再来回看上面的那个demo:

打开控制台可以看到,HTTP/1.1的方式,后面的图片的加载时间主要耗时在stalled,stalled的意思是从TCP连接建立完成,到真正可以传输数据之间的时间差。这就是队头阻塞,前面的请求没有处理,后面的请求都在排队等待。
这里例子我们能很直观的看到就是多路复用起到的优化作用。因为HTTP2 实现了请求并发,后面的请求不用再等待,加载时长当然少了很多。截一张HTTP2的图片加载耗时详情来看看(要看比较靠后的请求):

什么情况?我们发现后面的很多请求依旧有在排队哎,只是排队的时间相对1.1少了很多。一个TCP连接可以有任意数量的流,也就是同时可以并发任意数量的请求啊,为啥还会排队呢?原因就是请求太多时,浏览器或服务器会受不了,这超出了它的处理能力。流控制帮我们解决了这个问题,流控制会管理数据的传输,允许接收者停止或减少发送的数据量,免得接收方不堪重负。所以请求太多时,还是会存在排队等待的问题,因为不管是客户端或服务器端,能同时处理请求或响应都是有限的。
头部压缩也是HTTP2的一大亮点。在1.X版本中,首部用文本格式传输,通常会给每个传输增加500-800字节的开销。现在打开一个网页上百个请求已是常态,而每个请求带的一些首部字段都是相同的,例如cookie、user-agent等。HTTP2为此采用HPACK压缩格式来压缩首部。头部压缩需要在浏览器和服务器端之间:
HTTP2的静态字典是长这个样子的(只截取了部分,完整表格在这里(link:https://httpwg.org/specs/rfc7541.html%23static.table.definition)):

所以我们在传输首部字段的时候,例如要传输method:GET,那我们只需要传输静态字典里面method:GET对应的索引值就可以了,一个字节搞定。像 user-agent、cookie 这种静态字典里面只有首部名称而没有值的首部,第一次传输需要user-agent在静态字典中的索引以及他的值,值会采用静态 Huffman 编码来减小体积。
第一次传输过 user-agent 之后呢,浏览器和服务器端就会把它添加到自己的动态字典中。后续传输就可以传输索引了,一个字节搞定。
我们用 WireShark 来抓包验证一下:
HTTP2目前都是HTTPS的请求,WireShark 对 HTTPS 网站抓包解密请参考这里。
首次传输 user-agent 和第二次传输 user-agent


由于第一次传输的时候,字典里面并没有 user-agent 的值,这时候 user-agent 是 63 字节,第二次传输时,他已经在动态字典里面了,只传索引,一个字节搞定。
HPACK 的首部压缩力度

Header解码后的长度有471个字节,而HEADERS流只有246个字节。这只是第一个请求,后续的请求压缩力度会更大,因为前面请求用到的首部(静态字典中没有的)会添加到动态字典中,使得后续请求只需要传输字典里面的索引。

服务器端推送使得服务器可以预测客户端需要的资源,主动推送到客户端。
例如:客户端请求 index.html,服务器端能够额外推送 script.js 和 style.css。 实现原理就是客户端发出页面请求时,服务器端能够分析这个页面所依赖的其他资源,主动推送到客户端的缓存,当客户端收到原始网页的请求时,它需要的资源已经位于缓存。
针对每一个希望发送的资源,服务器会发送一个 PUSH_PROMISE 帧,客户端可以通过发送RST_STREAM帧来拒绝推送(当资源已经位于缓存)。这一步的操作先于父响应(index.html),客户端了解到服务器端打算推送哪些资源,就不会再为这些资源创建重复请求。当客户端收到 index.html 的响应时,script.js和style.css已经位于缓存。
想要搭一个 HTTP2 服务器的话推荐 node,很简单。链接

