HTTP/3 与 QUIC:为什么你的网站该开启它

从一次页面加载说起

打开一个现代网页,浏览器平均要发起 70 到 100 个请求。在 HTTP/2 时代,这些请求共享一条 TCP 连接,理论上很高效——直到其中一个数据包丢了。

TCP 是严格有序的字节流协议:第 5 个包丢了,即使第 6 到 100 个包已经到达,内核也不会把它们交给应用层。整个连接上的所有请求一起等,这就是传输层队头阻塞。

HTTP/2 在应用层做了多路复用,但底层依然是一条 TCP 连接,所以它解决不了这个问题——反而因为把所有请求都塞进一条连接,丢包的影响面更大了。

QUIC 的核心思路

QUIC 的做法是:别改 TCP 了,直接在 UDP 上重新实现一套传输层。

特性TCP + TLS 1.3QUIC
传输载体内核 TCP用户态 UDP
连接建立TCP 三次握手 + TLS 握手合并为 1-RTT
恢复连接重新完整握手0-RTT(有安全权衡)
丢包影响阻塞整条连接只阻塞受影响的流
连接迁移IP 变化即断开靠 Connection ID 保持
加密范围仅载荷含大部分头部
升级部署要改操作系统内核改用户态程序即可

其中连接迁移这一条经常被低估。手机从 Wi-Fi 切换到 4G 时 IP 地址会变,TCP 连接立刻失效,正在下载的文件要重新开始。QUIC 用与 IP 无关的 Connection ID 标识连接,切换网络后下载可以继续。

队头阻塞到底差多少

假设一条连接上有 6 个并发流,其中一个包丢失,重传需要 1 个 RTT:

text
HTTP/2 over TCP:
  [流1✓][流2✗丢了][流3✓][流4✓][流5✓][流6✓]
  └──────── 全部等待重传(1 RTT)────────┘

HTTP/3 over QUIC:
  [流1✓][流2✗重传中][流3✓][流4✓][流5✓][流6✓]
         └─ 只有流2等待,其余立即可用 ─┘

在丢包率 1% 以上的网络里(移动网络、跨国链路),这个差距会非常明显。在理想的有线网络下,两者的差别则几乎可以忽略。

在 Cloudflare 上开启

如果你的域名已经接入 Cloudflare,开启 HTTP/3 只需要点几下:

  1. 进入域名的 Speed → Settings
  2. 找到 HTTP/3 (with QUIC),打开开关
  3. 同时建议开启 0-RTT Connection Resumption

验证是否生效:

bash
# 看 alt-svc 响应头,h3 表示服务器支持 HTTP/3
curl -sI https://example.com | grep -i alt-svc

# 输出形如:
# alt-svc: h3=":443"; ma=86400

浏览器端可以在 DevTools 的 Protocol 列里直接看到每个请求用的是 h2 还是 h3。

什么时候不值得开

HTTP/3 不是银弹,以下场景要谨慎:

  • 企业内网有 UDP 限制:很多防火墙默认丢弃或限速 UDP 443,用户会静默回退到 HTTP/2
  • 需要基于 TCP 的中间设备:老旧的负载均衡、DDoS 清洗设备可能完全不认识 QUIC
  • 服务器 CPU 吃紧:QUIC 在用户态处理加解密,比内核 TCP 更耗 CPU
  • 需要精确的流量审计:QUIC 头部也被加密,传统基于端口的分析手段会失效

好在浏览器和服务器都支持优雅回退:客户端首次访问走 TCP,服务器通过 alt-svc 头告知"我也支持 h3",下次访问才会尝试 QUIC。所以开启 HTTP/3 几乎不会让现有用户变慢。

实践建议:先在 10% 流量上开启,观察 P95 首字节时间和错误率一周,再全量。

一个容易被忽略的细节:0-RTT 的风险

0-RTT 允许客户端在第一个包里就带上应用数据,看起来很美,但它不具备前向安全性,并且容易受到重放攻击——攻击者可以截获这个包并重复发送。

所以规范上明确要求:0-RTT 只能用于幂等请求。

http
GET /api/feed      ← 可以走 0-RTT
POST /api/transfer ← 绝对不能走 0-RTT

Cloudflare 默认只在安全的请求上启用 0-RTT,但这个约束在自建服务时经常被忽略。

小结

  • HTTP/2 的多路复用受限于 TCP,丢包时全线阻塞
  • QUIC 把传输层搬到 UDP 之上去掉了这个限制,代价是用户态开销
  • 连接迁移对移动端体验的提升往往比速度提升更有感
  • 开启方式极简,且有自动回退,风险很低
  • 真正要小心的是 0-RTT 的重放风险,只对幂等请求开放

对绝大多数网站来说,结论很简单:能开就开。