HTTP/2 与 HTTP/3 实际上发生了什么变化(以及你的 API 是否关心)
HTTP/3 使用 UDP 上的 QUIC 替代 TCP。这实际上对 API 延迟、CDN 行为以及你是否需要采取任何措施意味着什么。
简答:如果你位于CDN之后,HTTP/3很可能已经由CDN处理好了,无需额外配置。如果你自己运行服务器,HTTP/2已经可以满足95%的情况,而HTTP/3则是一个值得但并非紧急的升级。
以下是更详细的版本,因为协议差异背后的工程实现其实很有趣——了解这一点有助于你判断协议版本在哪些场景下真正重要。
HTTP/2 实际上解决了什么问题
HTTP/1.1 有两个问题,而 HTTP/2 正确地解决了这些问题。
加载一个页面需要太多 TCP 连接。 浏览器为并行获取资源,每个域名会打开 6 到 8 个 TCP 连接。每个连接都有设置开销(TCP 握手、TLS 协商),这种做法在大规模场景下是低效的。HTTP/2 用多路复用替代了这一方式:一个 TCP 连接,多个请求/响应流并行运行。不再需要连接切换。
每个请求都携带冗余的头部信息。 在 HTTP/1.1 中,每个请求都会发送完整的头部信息—— User-Agent, Accept-Encoding, Authorization,全部内容——即使与上一个请求没有变化。HTTP/2 的 HPACK 压缩通过在客户端和服务器之间维护一个共享查找表来去重头部信息。在 API 中,如果每次调用都发送相同的 Authorization: Bearer ... 令牌,这种优化在高流量场景下能显著降低开销。
服务器推送本应是第三个优势(预先发送客户端需要的资源),但实际中从未真正有效。Chrome 于 2022 年弃用了该功能。将其视为已废弃,不要在任何系统中构建相关功能。
HTTP/2 无法解决的问题
这里经常被忽略的部分是:在 TCP 上进行多路复用存在硬性上限。当一个 TCP 数据包在传输过程中丢失时,TCP 会暂停整个连接,直到该数据包被重传并确认。所有并行的 HTTP/2 流——无论哪个流包含丢失的数据包——都会处于空闲等待状态。这是 TCP 层的“头阻塞”(HOL)问题,这不是 HTTP/2 的缺陷,而是 TCP 的工作方式。
在可靠的有线连接上,丢包率低到几乎不会影响性能。但在移动网络、卫星链路或任何存在显著丢包的路径上,HTTP/2 的多路复用优势会消失。你可以在一个连接上复用多达 20 个流,而一个丢包就会导致所有 20 个流的传输停止。 😬
这不是一个可修复的限制,而是构建在字节流传输协议之上的请求多路复用协议的结构性缺陷——该协议本身并不知道“流”是什么。
HTTP/3 实际上改变了什么
HTTP/3 并不只是调整了帧格式或添加了一个新功能标志。它用 QUIC 替代了 TCP——QUIC 是运行在 UDP 上的传输协议。
QUIC 在传输层实现了多路复用,因此一个丢失的数据包只会阻塞它所属的流,而不会阻塞连接上的其他所有流。头阻塞问题在正确的协议栈层级得到了解决。这才是核心所在。
QUIC 还带来了一些其他功能:
- 连接迁移。 QUIC 通过连接 ID 而不是源 IP、源端口、目标 IP、目标端口的四元组来标识连接。在传输过程中从 Wi-Fi 切换到蜂窝网络,连接依然保持。这对移动应用很重要;对于服务器到服务器的 API 调用,这基本无关紧要。
- 0-RTT 握手。 对于已知服务器的连接,QUIC 可以跳过一次往返的 TLS 协商,立即发送数据。在重新连接时能带来真实的延迟优势。注意:0-RTT 存在重放攻击的风险——不要在非幂等端点上使用,除非你理解其权衡。
- 强制使用 TLS 1.3。 QUIC 不支持未加密的连接。没有 TLS 就没有 HTTP/3,绝对不行。
头部压缩也发生了变化:HTTP/3 使用 QPACK 而不是 HPACK。技术上,QPACK 避免了 HPACK 在跨流压缩头部时的阻塞问题。在实际使用中,你不会观察到明显差异;这只是一个基础设施层面的改进。
功能对比
| 特征 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输协议 | TCP | TCP | QUIC(UDP) |
| 请求多路复用 | 没有(多个连接) | 是的 | 是的 |
| 头阻塞 | 请求级 | TCP 级(仍然存在) | 没有任何 |
| 头部压缩 | 没有任何 | HPACK | QPACK |
| 需要 TLS | 不 | 没有(规范),是(浏览器) | 是(强制) |
| 0-RTT 握手 | 不 | 不 | 是的 |
| 连接迁移 | 不 | 不 | 是的 |
| 服务器推送 | 不 | 是(已废弃) | 否(已移除) |
这对 API 延迟意味着什么
HTTP/3 在特定条件下才会有帮助,并非在所有场景下都能普遍提升性能。
丢包网络。 在移动客户端通过不稳定的 LTE 或 5G 覆盖使用你的 API 时,会看到可测量的性能提升。数据中心之间在可靠私有网络上的 API 调用,丢包率几乎为零?性能差异可以忽略不计。
冷连接和重连。 如果你的 API 客户端频繁重连——例如短生命周期的无服务器函数、移动应用重启、临时容器——0-RTT 会降低连接建立成本。而长时间保持打开的连接则无法从中受益。
请求量很重要。 HTTP/2 和 HTTP/3 在客户端发起大量并行请求时表现最佳。浏览器并行加载 80 个资源时,多路复用能带来显著收益。而 REST API 依次发起 3 个请求时,收益则小得多。你客户端发起的并发请求越多,传输层的改进就越重要。
CDN 如何处理这个问题
Cloudflare 自 2019 年起支持 HTTP/3,并默认启用。AWS CloudFront 于 2022 年添加支持。Fastly、Akamai、BunnyCDN 等都支持它。
架构在这里很重要:你的 CDN 在边缘节点终止客户端连接。即使客户端通过 HTTP/3 使用 QUIC 连接到 CDN,CDN 到源站的链路几乎肯定是 HTTP/1.1 或 HTTP/2 通过 TCP。因此,在 CDN 上“启用 HTTP/3”不需要对你的源服务器进行任何更改。
如果你使用 Cloudflare,请检查你的速度 → 优化设置——HTTP/3(使用 QUIC)在大多数区域默认开启。其他 CDN 的设置可能不同。这是大多数团队可以做出的最具杠杆效应的变更:无需修改源站,即可为使用移动设备或高延迟路径的客户端带来即时收益。
你真的需要做些什么吗?
位于 CDN 之后: 检查 HTTP/3 是否已启用,然后继续。仅需 30 秒。
自行运行 nginx: HTTP/3 需要 nginx 1.25+(主线版本——稳定版落后于 QUIC)。你需要启用 --with-http_v3_module 编译标志和一个 listen 443 quic reuseport 指令,与现有的 TCP 监听器并存。确保防火墙开放 UDP 端口 443——某些网络配置会阻止该端口。如果你为移动终端用户提供服务,值得进行升级;如果你仅处理服务器间通信,则升级带来的中断不值得。
使用 Caddy: HTTP/3 默认启用,无需配置。无需任何操作。
构建调用第三方 API 的客户端: 你是否获得 HTTP/3 支持,取决于你调用的服务器,而不是你的代码。curl 从 7.86.0 版本开始支持 HTTP/3,需要兼容的后端(nghttp3 或 quiche)。目前 Python 和 Node 的大多数 HTTP 客户端库尚未原生支持 HTTP/3。Go 的标准 net/http 库不支持它;如果需要,有一个独立的 quic-go 库。
一个实用的注意事项:如果你需要测试服务器实际协商的 HTTP 版本,或构建带有 --http3 标志的 curl 命令, cURL 命令生成器 在 iotools.cloud 上非常有用,可以避免每次手动查找确切的标志语法。
