Q:HTTP/2 相比 HTTP/1.1 有哪些改进?HTTP/3 又解决了 HTTP/2 的什么问题?
HTTP/1.1、HTTP/2 和 HTTP/3 使用的 HTTP 方法、状态码和语义基本一致,主要区别在于:如何建立连接,以及如何在连接中传输多个请求和响应。
| 对比项 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 底层传输 | TCP | TCP | QUIC(基于 UDP) |
| 同一个源的常见连接方式 | 多条 TCP 连接 | 通常一条 TCP 连接 | 通常一条 QUIC 连接 |
| 并发方式 | 依靠多条连接 | 一条连接中的多个 HTTP/2 Stream | 一条连接中的多个 QUIC Stream |
| 消息传输 | 起始行和 Header 使用文本格式 | 二进制分帧 | 基于 QUIC Stream 的二进制帧 |
| Header 压缩 | 不支持协议级压缩 | HPACK | QPACK |
| 队头阻塞 | 有应用层和 TCP 层阻塞 | 解决应用层阻塞,仍有 TCP 层阻塞 | 没有 TCP 式跨流阻塞,单个 Stream 内仍需有序交付 |
| TLS | HTTP 可不使用,HTTPS 需要 TLS | 协议不强制,但浏览器通常使用 HTTPS | QUIC 集成 TLS 1.3 握手 |
| 网络切换 | 通常需要重新连接 | 通常需要重新连接 | 支持连接迁移 |
这里的连接数量都是常见实现,不是协议强制规定的固定数量。不同源、连接替换、代理和浏览器隔离策略都可能产生额外连接。
HTTP/1.1 默认支持持久连接,一条 TCP 连接可以被多个请求重复使用,并不需要每次请求都重新建立连接。
但是,HTTP/1.1 没有 Stream ID 来区分同一连接中的多个并发响应。它虽然支持请求管线化,可以连续发送多个请求,但服务器必须按照请求顺序返回响应,前面的响应较慢时,后面的响应仍然需要等待。因此浏览器实际通常不会依赖管线化,而是对同一个源建立多条 TCP 连接来实现并发:
HTTP/1.1 协议没有规定同一个源必须建立多少条连接。面试中经常提到的“浏览器同域名最多 6 条连接”只是常见实现策略,不是协议限制,具体数量可能因浏览器和运行环境而变化。
这种并发方式存在几个问题:
HTTP/2 引入二进制分帧,把请求和响应拆成 HEADERS、DATA 等帧。每个请求和响应属于一个带有 Stream ID 的 HTTP/2 Stream,不同 Stream 的帧可以在一条 TCP 连接中交错传输:
多路复用
多路复用可以直接理解为:一条连接同时承载多个 Stream。每个请求和对应响应使用一个独立的逻辑 Stream,但不需要为每个 Stream 重新建立 TCP 连接或进行 TLS 握手。
不同 Stream 的数据会被拆成带有 Stream ID 的帧,然后在同一条连接中交错传输:
接收方根据 Stream ID 把帧归类到对应的请求。这样多个请求可以同时发出,不需要等待某条连接空闲;某个请求的服务端处理较慢时,连接仍然可以传输其他 Stream 的数据。
二进制分帧
HTTP 消息被拆成可识别所属 Stream 的帧,使不同请求的数据能够在同一连接中交错传输。
HPACK Header 压缩
一个页面通常会请求 HTML、CSS、JavaScript、图片等大量资源,每个请求和响应都会携带 Header。其中 Cookie、User-Agent、Accept、Authorization 等内容经常重复出现;请求数量较多或 Cookie 较大时,反复发送完整 Header 会产生明显的额外开销。
HPACK 综合使用静态表、动态表、索引、字面量和 Huffman 编码。已经发送过的 Header 可以通过索引或只发送变化部分来表示,从而减少传输数据量,并不只是简单地“发送一个索引号”。
Header 压缩只负责压缩 HTTP 请求头和响应头,不负责压缩 HTML、JSON 等正文内容。正文通常由 gzip、Brotli 等内容编码负责压缩。
连接数量不等于并发能力。HTTP/2 的一条连接可以包含很多并发 Stream,因此它通常有以下优势:
HTTP/2 的所有 Stream 最终都通过同一条 TCP 字节流传输。TCP 必须按照字节顺序向上层交付数据,如果中间一个 TCP 报文段丢失,即使后面的数据已经到达,也需要等待丢失的数据重传。
因此,一个 TCP 报文段丢失可能让这条连接中的所有 HTTP/2 Stream 暂时无法继续交付数据。这就是 HTTP/2 仍然存在的 TCP 层队头阻塞。
在高丢包网络中,HTTP/2 多路复用的优势可能下降,但不能直接得出 HTTP/1.1 一定更快的结论,因为 HTTP/1.1 的多条连接也会增加握手、拥塞控制和服务器资源成本。
HTTP/2 协议定义了 Server Push,服务端可以在客户端请求前主动推送可能需要的资源。但它很难准确判断浏览器是否已经缓存资源,实际收益不稳定,Chromium 已默认禁用该能力。
因此面试中可以把 Server Push 作为协议特性提及,但不应再把它作为现代 Web 项目的主要优化手段。当前通常优先考虑 Preload 或 103 Early Hints。
HTTP/3 不再使用 TCP,而是使用基于 UDP 的 QUIC。这里的“基于 UDP”不代表 HTTP/3 不可靠:QUIC 自己实现了可靠传输、丢包重传、流量控制、拥塞控制和加密。
HTTP/3 通常对同一个源复用一条 QUIC 连接,并在连接中创建多个 QUIC Stream:
QUIC Stream 是一条 QUIC 连接内部的逻辑数据通道,不是一条独立网络连接。创建 Stream 不需要重新进行网络连接或 TLS 握手。
它具有以下特点:
HTTP/3 的一个请求和对应响应通常使用一个客户端创建的双向 QUIC Stream。控制信息和 QPACK 状态则使用单向 Stream。
减少跨 Stream 的传输层队头阻塞
某个 Stream 丢包时,主要由该 Stream 等待重传,不会像 TCP 那样要求其他 Stream 等待缺失的字节补齐后才能继续交付。
QPACK Header 压缩
HTTP/3 使用适配 QUIC 多 Stream 模型的 QPACK。它的目标与 HPACK 相同,都是减少重复 Header,但处理动态表和到达顺序的方式不同。
更快的连接建立
QUIC 将传输连接和 TLS 1.3 握手结合起来,首次连接通常可以用 1-RTT 建立。只有客户端和服务器之间存在之前连接保存的会话信息,并且服务器允许时,才能尝试使用 0-RTT 提前发送数据。
0-RTT 数据存在重放风险,因此并不是所有请求都适合使用,也不能把 HTTP/3 简单理解为“每次都是 0-RTT”。
连接迁移
QUIC 使用 Connection ID 识别连接,不完全依赖 IP 和端口。当设备从 Wi-Fi 切换到移动网络时,可以验证新路径并继续使用原来的 QUIC 连接。
不一定。HTTP/3 在高延迟、存在一定丢包或网络切换的场景中通常更有优势,但也存在以下成本:
同一连接中的响应需要按照请求顺序返回。前面的响应慢,后面的响应也需要等待,因此存在应用层队头阻塞。浏览器通过建立多条连接缓解这个问题。
多个请求通过不同 Stream 交错传输,解决了 HTTP/1.1 的应用层队头阻塞。但所有 Stream 仍然共享一条有序 TCP 字节流,因此仍有 TCP 层队头阻塞。
QUIC 在传输层直接提供多个 Stream。单个 Stream 内仍然要求有序交付,但一个 Stream 等待丢包重传时,不会阻止其他 Stream 继续交付数据。
HTTP/2 的 Stream 只存在于 HTTP/2 层,底层 TCP 并不知道数据属于哪个 Stream。TCP 看到的是一条统一编号、必须按顺序交付的字节流:
即使数据包 2 和数据包 3 已经到达,TCP 也要等待数据包 1 重传后,才能继续按顺序向 HTTP/2 交付数据。因此 Stream 1 的丢包可能让 Stream 3 和 Stream 5 一起等待。
QUIC 则在传输层直接管理多个 Stream,并分别维护每个 Stream 的数据顺序:
所以 HTTP/3 避免的是不同 Stream 之间因为统一传输顺序产生的阻塞,并不是完全没有阻塞:
可以总结为:
这些都是常见模型,不是绝对限制。
QUIC 连接是真正维护网络状态、安全状态和拥塞控制的连接;QUIC Stream 是连接内部的数据通道。多个 Stream 共用连接资源,但分别维护各自的数据顺序和传输状态。
不是简单因为 UDP 更快。UDP 只为 QUIC 提供基础的数据报传输能力,真正的改进来自 QUIC 自己实现的多 Stream、集成 TLS 握手、连接迁移以及更灵活的传输控制。
HTTP/1.1 默认支持持久连接,但为了并发加载资源,浏览器通常需要对同一个源建立多条 TCP 连接,每条连接中的请求主要依次处理。HTTP/2 使用二进制分帧和多路复用,在一条 TCP 连接中通过多个 Stream 并发传输请求,并使用 HPACK 压缩 Header,因此能减少连接建立、请求排队和重复 Header 的开销;但所有 Stream 仍然共享 TCP 的有序字节流,所以存在 TCP 层队头阻塞。HTTP/3 将底层换成 QUIC,在一条 QUIC 连接中使用相对独立的 QUIC Stream,并使用 QPACK 压缩 Header,一个 Stream 丢包不会阻止其他 Stream 继续交付数据;同时还支持更快的连接建立和连接迁移。