大家好,我是前端西瓜哥。今天我们来聊聊 HTTP/2 的多路复用。
HTTP/1 下的请求,并不能很好地地利用带宽:一个 TCP 连接同时只能有一个 HTTP 请求和响应。如果正在发送一个 HTTP 请求,那其他的 HTTP 请求就得排队。
这种排队会产生一个请求队列,当队头的请求发生意外(比如丢包、服务器响应缓慢),导致比平时要慢得多,就会导致后面的请求被延迟。这种情况我们称为 队头阻塞(Head-of-line blocking)。
为了缓解这个问题,浏览器会对同一个域名建立多个 TCP 连接,来实现 HTTP 的并发。
但这也对服务器造成不小的负担,所以浏览器做了限制,同一个域名下 TCP 连接数最多会在 6 ~ 8 个左右。
如果网页一次性加载的资源太多,比如大量图片,6 个 TCP 连接数可能也会顶不住。为了解决一个问题,我们会使用 域名分片(Domain sharding) 的方法,就是将资源放到不同的域名下。
比如将图片放到专门的 static.xxx.com
,或者 CDN。因为域名不同,所以总的 TCP 连接数就能突破 6 的限制。达到 域名数 x 6
。
HTTP/1.1 有一个 pipeline 机制,意图解决不能并发的问题,但因为实现上的缺陷,实质上已经废弃。浏览器也默认关闭 pipeline。
为了解决这个问题,HTTP/2 使用了 多路复用。
HTTP/2 引入了流(stream)和帧(frame)的概念。
帧是最小的数据单位,HTTP 报文不再是原来的明文的 ASCII 编码,而是会被拆分成一个个的二进制形式的帧。帧上面除了 HTTP 数据,还包含数据长度、流标识符、帧类型等信息。
流是一个建立连接后的双向的虚拟字节流,可以承载多个消息。帧通过自己的流 ID,确定自己属于哪个报文,就可以不按顺序进行请求响应了。
HTTP/2 会将所有 HTTP 请求打散成帧,在一个 TCP 连接上做并发请求,充分利用 TCP 带宽。现在浏览器对于 HTTP2,只会建立一个 TCP 连接,减轻了服务端不小压力。
我们举个例子讲解 HTTP/1 升级为 HTTP/2 后利用多路复用带来的优势。
假设依次请求一个很大的 JS 文件,和一个很小的 CSS 文件。
在 HTTP/1 时,TCP 的发送的包是这样的(JS 用多个 1 表示,CSS 用多个 2 表示):
111111111111111111111111222
JS 很大,会让 CSS 延迟,我们可能希望比较小的 CSS 能早一点请求完,早一点做解析。而且 JS 一旦发生了意外发生阻塞,CSS 就更晚才能获取到了。
现在我们用 HTTP/2,就变成了下面这样:
121212111111111111111111111
因为并行的原因,CSS 不仅不用再担心 JS 导致的阻塞,还能更早请求并获取到资源。
HTTP/2 的多路复用能够解决 HTTP 队头阻塞问题,更充分地利用 TCP 带宽。
但因为还是在 TCP 上的协议,所以不能解决 TCP 队头阻塞问题,这个问题要交给 HTTP/3 通过 UDP 来解决了,期待一下。
我是前端西瓜哥,欢迎关注我,学习更多前端知识。