HTTP/3 总览
TCP 队头阻塞为什么无法在 TCP 上解决,以及 h3 = HTTP/2 语义 + QUIC 传输。
作者 David更新于 第 10 篇(共 16 篇)
唯一的动机
HTTP/2 消除了 HTTP 层的队头阻塞,但所有流仍共享一条 TCP 连接:
TCP 是一条有序字节流。内核的契约是:
「按发送顺序、无缺口地把字节交给应用」
发送: [流1-A][流3-B][流1-C][流5-D]
↑ 丢失
接收: 流1-A 已交付
流3-B 丢了 → 内核必须等重传
流1-C、流5-D 已完整到达,但被扣在内核缓冲区
→ 三个流一起卡住,等一个与它们无关的包根源在于 TCP 不知道流的存在。它只看到一条字节流,缺口就是缺口。这一点无法在 TCP 之上修复——无论 HTTP 层怎么设计,都改变不了内核的交付语义。
要解决,只能换传输层:让传输层自己知道有多个独立的流,某个流丢包时不影响其他流的交付。这就是 QUIC,也是 HTTP/3 存在的全部理由。
为什么不改 TCP
理论上可以给 TCP 加多流支持(SCTP 就是这么做的),实际不可行:
| 障碍 | 说明 |
|---|---|
| 中间件僵化(ossification) | 全球的 NAT、防火墙、负载均衡器只认识 TCP/UDP。新传输协议号的包会被大量丢弃 |
| 内核部署周期 | TCP 实现在操作系统内核里,一个改动要等数年才能覆盖大多数设备 |
| TCP 头部明文 | 中间件会窥探甚至改写 TCP 选项,导致新特性被破坏 |
QUIC 的对策:
- 跑在 UDP 上 —— UDP 到处都通,无需中间件升级。
- 实现在用户态 —— 随浏览器/服务端软件更新,迭代周期从年降到周。
- 几乎全部加密 —— 连包序号和大部分头部都加密,中间件看不到也改不了,从根上防止再次僵化。
HTTP/3 = HTTP/2 语义 + QUIC
分层对比:
HTTP/1.1 HTTP/2 HTTP/3
─────────────────────────────────────────
HTTP 语义 HTTP 语义 HTTP 语义 ← 三版完全相同 (RFC 9110)
─────────────────────────────────────────
文本报文 二进制帧 二进制帧 ← h3 帧格式与 h2 相似但不同
HPACK QPACK ← 为乱序交付重新设计
流/多路复用 ↓ 下沉到 QUIC
流量控制 ↓ 下沉到 QUIC
─────────────────────────────────────────
TLS (可选) TLS (事实必需) ↓ 内建于 QUIC
─────────────────────────────────────────
TCP TCP QUIC
UDPHTTP/3 把 h2 的三样东西下沉到了传输层:流的多路复用、流量控制、连接管理(部分)。h3 自己只剩下「帧格式 + 字段压缩 + HTTP 语义映射」。
结果是 h3 规范(RFC 9114)比 h2 规范短得多——重活都交给了 QUIC。
相对 h2 的改进清单
| 改进 | 说明 |
|---|---|
| 消除传输层队头阻塞 | 一个流丢包只阻塞该流,其他流照常交付 |
| 握手更快 | TCP+TLS1.3 需 2–3 RTT;QUIC 1 RTT,会话恢复 0 RTT |
| 连接迁移 | 用 Connection ID 而非四元组标识连接,换 IP/网络不断连(WiFi 切 4G 无感) |
| 更好的丢包恢复 | 单调递增包号消除 TCP 的重传歧义;用户态可用更先进的拥塞控制 |
| 强制加密 | 传输层元数据也加密,抗窥探与抗僵化 |
| 无 CONTINUATION 阻塞 | 帧结构简化,字段块无需连续独占连接 |
代价
诚实地列出来:
- CPU 开销更高。UDP 的内核路径优化远不如 TCP(无 GSO/GRO 硬件卸载的历史积累),加解密也在用户态。实测 h3 的 CPU 消耗常为 h2 的 2–3 倍,虽在快速改善。
- UDP 可能被封禁或限速。部分企业网络与运营商对 UDP 有限制,客户端必须保留回落到 h2/1.1 的能力。
- 实现成熟度。QUIC 栈自己实现了拥塞控制、丢包恢复、加密——这些在 TCP 里是内核数十年打磨的成果。
- 可观测性下降。传输层加密后,运维再也不能靠抓包分析 RTT、重传、窗口——排障需依赖端点自己导出的 qlog。
版本标识与发现
- ALPN 标识:
h3。 - 因为客户端无法预知服务端是否支持 UDP/QUIC,h3 必须先被发现——通过
Alt-Svc字段或 DNS HTTPS 记录。 - 首次访问几乎总是走 h2/1.1,随后才升级到 h3。
下一篇:QUIC 传输层。