HTTP/2是HTTP协议自1999年HTTP 1.1的改进版RFC 2616发布后的首个更新,前身是SPDY协议(Google),于2015年2月17日被批准。
HTTP/2标准于2015年5月以RFC 7540正式发表,多数主流浏览器已经在2015年底支持了该协议。目前国内外大多数网站也都已经支持了HTTP/2,比如 Google/Stackoverflow/Reddit,国内的 淘宝/Segmentfault/掘金/CSDN/博客园/36Kr等等都已经全面支持了HTTP/2协议
HTTP/2 相比HTTP/1 来说,主要是性能上的大幅提升,而且完全没有没有改动 HTTP/1协议中的应用语义。 Method、State Code、URI 和Header等核心概念完全没有变化
下面详细介绍HTTP/2 中的一些关键升级点
二进制的分层(Binary Framing Layer)
Binary Framing Layer 的设计,算是HTTP/2性能提升的核心了。HTTP/2中在应用层又设计了一套BinaryFrame Layer,它定义了HTTP消息在传输过程中的封装方式。不过这个Frame Layer和TCP 的 Packet可不是一回事,这个Frame Layer只是逻辑上的分层,在HTTP和TCP层之间,类似于Http Chunk
如上图所示,HTTP/2中的报文,在传输前都会被先构建成一个个的帧(Frame),每次Socket发送的最小单位是一个帧,每个帧都以二进制格式进行编码
二进制格式编码(Binary format encode)
在HTTP/1中,数据都是以文本编码的模式进行传输的。那么什么叫文本编码,什么叫二进制编码呢?
举个例子,协议中有一个长度的首部值为 11
,这个数字在文本编码中(用字符串来表示),它会占用2个Byte,对应的字节为[49, 49]
,那么在二进制编码下,11
如果是Unsigned Int类型,那么它会占用4个Byte,对应的字节为[0, 0, 0, 11]
。
上面这个例子,看起来二进制编码下占用更大了;其实大多数情况下,二进制编码的占用会更低。如果换个大点的数字2147483647
,在文本编码下需要占用10个Byte,可二进制编码下还是只需要占用4个Byte
文本编码(Byte Array) | 二进制编码(Byte Array) | |
---|---|---|
11 | [49, 49] | [0, 0, 0, 11] |
2147483647 | [50, 49, 52, 55, 52, 56, 51, 54, 52, 55] | [127, -1, -1, -1] |
二进制格式这个叫法虽然比较容易引起歧义,不过大家都这么叫,那就是对的
不过既然都用二进制编码了,那么还能叫超文本传输(HyperText Transfer )吗……
比如在HTTP/1中,有一个Chunk编码,和上面提到的Binary Framing Layer有些相似,都是在TCP之上加了一层逻辑层。Chunk编码中有一个Length字段,就是用文本编码的,但Binary Framing 中的长度和其他字段都是用二进制编码,所以这也是HTTP/2新增的逻辑层叫Binary Framing的原因吧
二进制格式虽然占用更小,但不像文本编码那种直观,易于调试,肉眼很难直接看出数据的内容
流/消息/帧(Stream/Message/Frame)
- Stream - 流,已建立的双向字节流,是一条逻辑的链路,对应一次HTTP交互的完整请求和响应
- Message - 消息,一次HTTP的请求消息,或者响应消息;一次请求-响应的交互报文,属于一个Stream
- Frame - 帧,HTTP/2 传输的最小单位,每个帧包含帧首部,首部中会包含所属流的ID,一个Message会被分成多个Frame进行传输,所属不同Stream的Frame可以交错发送,对端收到交错的Frame后再根据Stream进行组装
在HTTP/2中,(同源的)所有的通信都会在一个单一的 TCP 连接上执行,该连接可以传输任意数量的Stream
如上图所示:一条TCP连接,可以承载多条Stream,一条Stream包含一次完整的请求和响应消息,请求和响应消息又会被拆分为多个Binary Frame进行传输
多路复用
HTTP/2中的多路复用,和I/O多路复用(I/O Multiplexing)可不是一回事。I/O多路复用指的是同一个进程监听多个Sock事件,而HTTP/2中的多路复用是指在一条TCP连接上同时进行多个请求和响应消息的传输。
**
在HTTP/1中,如果想并行的发送多个请求,那么需要建立多条TCP连接,每个连接同时只能处理一个请求;而且当TCP连接过多时,还会造成TCP的排队
HTTP/2中新的Binary Frame Layer的设计解决了这个弊端,客户端和 服务器可以把 多条并行的HTTP 消息分解为互不依赖的Frame(如下图所示),然后交错发送,最后对端会把这些交错的Frame按Stream分组重新组合起来
上图中包含了一个连接上多个传输中的Stream:Client正在向服务端发送Stream5的Data Frame,同时Server也在向Client交错发送Stream1和Stream3的Frame,在这条TCP连接上有3个请求 /响应的数据进行交互
多路复用是HTTP/2性能提升的核心,这种方案的优势主要在于以下几点:
- 并行处理多个请求/响应,不会发生应用层面的阻塞
- 使用单个TCP连接,大幅减少资源占用
这里可能会有一个疑问,换成单个TCP连接真的会更快吗?
其实不绝对,HTTP/1中浏览器一般会限制单个域名的连接数。比如Chrome会限制单个域名连接数上限为6个(历史数据,不保证准确,可能不同版本有所差异),如果在带宽足够,负载也不高时,同时下载的文件在6个以内时,单连接和多连接速度没什么区别。
不过如果同时下载的文件超过6个时,超出6个的那些下载就需要排队等待,这个时候就有区别了,HTTP/1中的连接模型会导致排队,而HTTP/2不会
针对上述问题也有一些解决方案,比如说Domain Sharding,将静态资源分别部署至多个域名站点,这样就可以一定程度上绕过浏览器的连接限制;或者时拼接静态资源,将多个静态资源文件合并至一个,降低连接数的占用
而HTTP/2中,根本就不用考虑连接数的限制,因为它对同源的下载只会使用一个TCP连接,自然也就没什么连接限制的概念了。
不过这里可能会思考一个问题,多个请求的数据在单个TCP连接中传输时,会发生阻塞或者排队吗?
其实不会,TCP滑动窗口大小的上限是65535,足够跑满你的带宽。不过在网络环境极其差的时候,还是会有些影响的,毕竟收到ACK太慢或丢包,不过这种情况下单连接和多连接都一样有问题;但在网络情况良好时,单连接省去了额外的建立连接的开销,而且在TCP的Slow Start的机制下,连接刚建立时滑动窗口的大小是比较小的,传输报文的速率会有所降低;不过在HTTP/2下,由于只有一条连接,且多个请求的报文可以交错发送不用等待,上述的缺点都可以避免了。
总结一下,HTTP/2的多路复用机制并不是在所有场景下都能够提升性能,只是在浏览器同时加载过多文件时才会大幅提升性能,并行的请求越多HTTP/2和HTTP/1的差异会越明显,如果只是下载单个文件,这个多路复用并不能带来什么提升
这个网站,提供了一个HTTP/2和HTTP/1在同时加载多个资源时的速度差异:https://http2.akamai.com/demo,这个网页中同时下载400个小图片,可以看到HTTP2的加载速度远超HTTP/1
不过HTTP/2的优化可不止连接复用,还有首部压缩,流量控制等
首部压缩(Header Compression)
在HTTP/1中,报文中的Header时通过文本形式编码的,就像下面这样:
每个header name都会跟一个冒号,然后时一个可选的空格,每个header以CRLF结尾,最后还会保留一个空行作为header部分的结束标识
这些header每次传输会增加500-800字节的开销,如果算上HTTP cookie的化,甚至会增加上千个字节开销。为了减少此开销,HTTP/2使用HPACK压缩格式压缩请求和响应Header数据:
- 通过静态霍夫曼编码(Huffman code)对发送的Header进行编码,降低Header的大小
- 要求客户端和服务器都维护和更新以前看到的Header的索引列表,然后将该列表用作有效编码先前传输的值的参考
客户端和服务端都会建立一个Header索引表,里面包含已经发送或接收的header,当再次发送header数据时会先从这个header索引表中查找,如果找到就用特殊值替换这个重复header,这样降低了重复值的占用,减小了报文大小
如上图所示,Request 1和Request 2的header只有一个:path不同,那么在发送Request 2时其他只有:path需要完整编码发送,其他header替换成对应重复的索引即可
总结
HTTP/2性能提升的关键,主要在于新的二进制分层配合多路复用设计,降低了连接数的占用,浏览器环境下提升会比较明显
早期的域名域名分片(Domain Sharding),资源文件合并(Bundle resources )这种针对HTTP/1的优化策略,在HTTP/2下也完全不需要了;如果升级到HTTP/2,前端的一些构建工具最好也将构建策略调整一下,单域名+多文件会更适合HTTP/2。
不过对于一些纯服务端环境下,比如服务端调用三方系统的HTTP接口,不会有什么连接数的限制,但是单连接下还是可以降低建立连接的开销
加上HTTP/2的Header压缩,降低了HTTP的报文大小,进一步的提升了HTTP/2的性能