为什么需要 HTTP/3

HTTP/2 虽然引入了多路复用、头部压缩、服务器推送等优化,但仍存在根本性问题:

队头阻塞(Head-of-Line Blocking)

HTTP/2 over TCP:
Stream 1: [Packet 1] [Packet 2] [Packet 3]  ← 如果 Packet 2 丢失
Stream 2: [Packet 1] [Packet 2] [Packet 3]  ← 整个连接被阻塞
Stream 3: [Packet 1] [Packet 2] [Packet 3]  ← 必须等待重传

TCP 是字节流协议,它不理解 HTTP/2 的流概念。一个包丢失会导致整个 TCP 连接阻塞,所有流都受影响。

QUIC 协议设计

核心特性

QUIC (Quick UDP Internet Connections):
┌─────────────────────────────────────────┐
│              QUIC 头部                   │
│  Connection ID | Packet Number | Flags  │
├─────────────────────────────────────────┤
│              加密载荷                    │
│  (TLS 1.3 + 自定义加密)                 │
├─────────────────────────────────────────┤
│              流控制                      │
│  Stream 1: 独立流控                     │
│  Stream 2: 独立流控                     │
│  Stream 3: 独立流控                     │
└─────────────────────────────────────────┘

0-RTT 连接建立

传统 TCP + TLS 1.3:
客户端 → 服务器: SYN
服务器 → 客户端: SYN-ACK
客户端 → 服务器: ACK + ClientHello
服务器 → 客户端: ServerHello + Finished
客户端 → 服务器: Finished
客户端 → 服务器: HTTP 请求
总延迟: 2-RTT (TCP) + 1-RTT (TLS) = 3-RTT

QUIC:
客户端 → 服务器: Initial + ClientHello + HTTP 请求
服务器 → 客户端: ServerHello + Finished + HTTP 响应
总延迟: 1-RTT (首次) / 0-RTT (恢复)

连接迁移

TCP 连接由四元组标识:(源IP, 源端口, 目标IP, 目标端口)

WiFi → 4G 切换:
TCP:  四元组变化 → 连接断开 → 重新建立
QUIC: Connection ID 不变 → 无缝迁移

QUIC 使用 Connection ID 而非四元组标识连接,网络切换时连接不中断。

HTTP/3 架构

HTTP/3 协议栈:
┌─────────────────────────────────────┐
│           HTTP/3 (应用层)            │
├─────────────────────────────────────┤
│           QUIC (传输层)              │
├─────────────────────────────────────┤
│           UDP                       │
├─────────────────────────────────────┤
│           IP                        │
└─────────────────────────────────────┘

流多路复用

QUIC 流复用:
Stream 1: [Frame 1] [Frame 2] [Frame 3]  ← 独立流控
Stream 2: [Frame 1] [Frame 2] [Frame 3]  ← 独立流控
Stream 3: [Frame 1] [Frame 2] [Frame 3]  ← 独立流控

一个流丢包 → 只影响该流,其他流继续传输

丢包恢复

TCP 丢包恢复:
Packet 2 丢失 → 等待超时 → 重传 → 所有流阻塞

QUIC 丢包恢复:
Packet 2 丢失 → 只重传 Stream 2 → 其他流不受影响

性能对比

延迟对比

场景HTTP/1.1HTTP/2HTTP/3
首次连接3-RTT3-RTT1-RTT
连接恢复1-RTT1-RTT0-RTT
网络切换断开重连断开重连无缝迁移
丢包恢复全连接阻塞全连接阻塞单流恢复

带宽利用

HTTP/2 over TCP:
- TCP 拥塞控制:全局共享
- 一个流慢 → 所有流受影响
- 无法充分利用带宽

HTTP/3 over QUIC:
- 每个流独立拥塞控制
- 一个流慢 → 其他流不受影响
- 更好的带宽利用

实际部署

Nginx 配置 HTTP/3

server {
    listen 443 quic;
    listen 443 ssl;
    
    http2 on;
    
    # QUIC 参数
    add_header Alt-Svc 'h3=":443"; ma=86400';
    
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    
    # TLS 1.3 配置
    ssl_protocols TLSv1.3;
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
}

客户端支持

# Chrome/Edge: 自动支持
# Firefox: 需要手动启用
about:config → network.http.http3.enable = true

# curl 测试
curl --http3 https://example.com

QUIC 的挑战

UDP 限制

问题:
1. 部分运营商限制 UDP 流量
2. 企业防火墙可能阻止 UDP
3. 某些 NAT 设备对 UDP 支持不好

解决方案:
1. 使用 UDP 443 端口(最常用)
2. 降级到 TCP(回退机制)
3. 使用中间件代理 QUIC 流量

拥塞控制

QUIC 实现了多种拥塞控制算法:

// Google BBR 拥塞控制
struct bbr {
    uint64_t bandwidth;      // 带宽估计
    uint64_t rtt;            // 延迟估计
    uint32_t cwnd;           // 拥塞窗口
    uint32_t pacing_rate;    // 发送速率
};

面试常见问题

  1. HTTP/3 为什么基于 UDP?

    • TCP 协议栈固化在内核中,难以修改
    • UDP 提供灵活的传输层,可以在用户态实现新特性
    • 避免 TCP 的队头阻塞问题
  2. QUIC 如何保证可靠性?

    • 在 UDP 上实现类似 TCP 的可靠性机制
    • 使用 ACK 帧确认接收
    • 实现超时重传和快速重传
  3. 0-RTT 的安全性?

    • 0-RTT 数据可能被重放攻击
    • 只用于幂等请求(如 GET)
    • 服务器可选择性接受 0-RTT 数据
  4. HTTP/3 的缺点?

    • UDP 可能被限制
    • 实现复杂度高
    • 调试工具支持有限

实战:QUIC 性能测试

# 安装 quic-trace
go install github.com/marten-seemann/quic-trace@latest

# 分析 QUIC 连接
quic-trace client www.example.com:443

# 使用 qvis 可视化
# https://qvis.quictools.info/