由于 HTTPS 在安全方面已经做的非常好了,HTTP 改进的关注点放在了性能方面。主要区别有:
feature | description |
---|---|
头部压缩 | hpack + 哈夫曼编码压缩 |
多路复用 | 报文转为二进制,生成 steam 在一个 tcp 乱序传输,但是同一个 Stream ID 的帧有序传输 |
服务器推送 | 新建“流”主动向客户端发送消息 |
可以设置请求优先级 |
头部压缩
HTTP1.1 有着对请求体的压缩 Content-Encoding
// 发送端
Content-Encoding: gzip
// 接收端
Accept-Encoding: gzip
但报文的另一个组成部分——Header
却被无视了,没有针对它的优化手段。
- 当请求字段非常复杂的时候,尤其对于 GET 请求,请求报文几乎全是请求头,这个时候还是存在非常大的优化空间的。
- 成千上万的请求响应报文里有很多字段值都是重复的,非常浪费,“长尾效应”导致大量带宽消耗在了这些冗余度极高的数据上。
HTTP2 针对头部字段,也采用了对应的压缩算法——HPACK,对请求头进行压缩。
:::: tabs ::: tab 1. 废除了原有的起始行概念 为了方便管理和压缩,HTTP/2 废除了原有的起始行概念
把起始行里面的请求方法、URI、状态码等统一转换成了头字段的形式,并且给这些“不是头字段的头字段”起了个特别的名字——“伪头字段”(pseudo-header fields)。而起始行里的版本号和错误原因短语因为没什么大用,顺便也给废除了。
为了与“真头字段”区分开来,这些“伪头字段”会在名字前加一个“:”,比如“:authority” “:method” “:status”,分别表示的是域名、请求方法和状态码。
现在 HTTP 报文头就简单了,全都是“Key-Value”形式的字段, :::
::: tab 2. 在服务器和客户端之间建立哈希表
将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如 0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。
:::
::: tab 3. 对于整数和字符串进行哈夫曼编码 哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。 ::: ::::
头部数据压缩之后,HTTP2 就要把报文拆成二进制的帧准备发送。
多路复用
什么是 HTTP 队头阻塞?
我们之前讨论了 HTTP 队头阻塞的问题,其根本原因在于 HTTP 基于请求-响应的模型,在同一个 TCP 长连接中,前面的请求没有得到响应,后面的请求就会被阻塞。
后面我们又讨论到用并发连接和域名分片的方式来解决这个问题,但这并没有真正从 HTTP 本身的层面解决问题,只是增加了 TCP 连接,分摊风险而已。而且这么做也有弊端,多条 TCP 连接会竞争有限的带宽,让真正优先级高的请求不能优先处理。
而 HTTP/2 便从 HTTP 协议本身解决了队头阻塞问题。注意,这里并不是指的 TCP 队头阻塞,而是 HTTP 队头阻塞,两者并不是一回事。TCP 的队头阻塞是在数据包层面,单位是数据包,前一个报文没有收到便不会将后面收到的报文上传给 HTTP,而 HTTP 的队头阻塞是在 HTTP 请求-响应层面,前一个请求没处理完,后面的请求就要阻塞住。两者所在的层次不一样。
那么 HTTP/2 如何来解决所谓的队头阻塞呢?
二进制分帧
首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输 01 串,方便了机器的解析。
原来 Headers + Body 的报文格式如今被拆分成了一个个二进制的帧,用 Headers 帧存放头部字段,Data 帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。
通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。
可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?
首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能,我们放到下一节再来介绍。
服务器推送
HTTP/2 还在一定程度上改变了传统的“请求 - 应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端,减少等待的延迟,这被称为“服务器推送”(Server Push,也叫 Cache Push)。