Skip to content
On this page

由于 HTTPS 在安全方面已经做的非常好了,HTTP 改进的关注点放在了性能方面。主要区别有:

featuredescription
头部压缩hpack + 哈夫曼编码压缩
多路复用报文转为二进制,生成 steam 在一个 tcp 乱序传输,但是同一个 Stream ID 的帧有序传输
服务器推送新建“流”主动向客户端发送消息
可以设置请求优先级

头部压缩

HTTP1.1 有着对请求体的压缩 Content-Encoding

js
// 发送端
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)。

Released under the MIT License.