21.5 网络优化:HTTP/3 / CDN / Brotli

深入解析 HTTP/3 QUIC 协议、CDN 边缘缓存策略、Brotli 压缩与资源推送的网络层优化原理与实战

HTTP/3CDNBrotliQUIC压缩边缘计算

原理

网络层优化是前端性能中最容易被忽视却收益最高的领域。HTTP 协议演进、CDN 边缘架构和文本压缩算法共同决定了资源从服务器到浏览器的传输效率。理解这些底层机制,可以帮助开发者在架构层面做出正确的技术决策。

HTTP/2 的多路复用与头部压缩

HTTP/2(2015)引入了三个关键特性:

二进制分帧(Binary Framing)

HTTP/2 将通信分解为二进制编码的帧(Frame),每个帧属于特定的流(Stream)。请求和响应被拆分为多个帧交错发送,在单一 TCP 连接上实现真正的多路复用(Multiplexing)。这解决了 HTTP/1.1 的队头阻塞(Head-of-Line Blocking)问题——在 HTTP/1.1 中,浏览器对每个域名只能建立 6~8 个并发连接,且每个连接上请求必须串行响应。

头部压缩(HPACK)

HTTP/2 使用 HPACK 算法压缩请求头。通过静态表(预定义常见头字段)、动态表(连接级缓存已发送的头字段)和 Huffman 编码,HPACK 可将典型请求头压缩 80% 以上。这对于携带大量 Cookie 的请求尤为有效。

服务器推送(Server Push)

HTTP/2 允许服务器在客户端请求 HTML 时,主动推送 CSS 和 JS 资源。但由于推送资源可能与浏览器缓存冲突,且优先级控制复杂,Chrome 已于 2022 年移除对 HTTP/2 Server Push 的支持,转而推荐 103 Early Hints。

HTTP/3 与 QUIC:基于 UDP 的革命

HTTP/3(2022)是 HTTP 协议的下一个重大演进,其底层传输从 TCP 切换为 QUIC(Quick UDP Internet Connections)。

TCP 的固有缺陷:

  1. 握手延迟:TCP 需要 1-RTT(或 TLS 1.2 下 2-RTT)建立连接
  2. 队头阻塞:TCP 保证字节流顺序,若一个包丢失,后续所有包必须等待重传
  3. 连接迁移困难:TCP 连接由四元组(源 IP、源端口、目的 IP、目的端口)标识,网络切换(如 Wi-Fi -> 4G)必须重建连接

QUIC 的核心改进:

| 特性 | TCP + TLS | QUIC | |------|-----------|------| | 握手延迟 | 1-3 RTT | 0-1 RTT(重连时 0-RTT) | | 队头阻塞 | 传输层阻塞影响所有流 | 基于 UDP,每个流独立,单流丢包不影响他流 | | 连接迁移 | 需重建连接 | 连接 ID 与 IP 解耦,支持无缝迁移 | | 安全性 | TLS 叠加在 TCP 之上 | TLS 1.3 内建,无冗余握手 |

HTTP/3 的流优先级:

HTTP/3 使用 QPACK(QUIC 版 HPACK)替代 HPACK 进行头部压缩,并引入了更灵活的流优先级机制。由于 QUIC 的每个流在传输层独立,高优先级资源(如关键 CSS)不会因低优先级资源(如图片)的丢包而延迟。

CDN 的边缘缓存架构

CDN(Content Delivery Network)通过在全球分布的边缘节点缓存静态资源,将内容推近用户,降低网络延迟。

CDN 的工作流程:

  1. DNS 解析:用户请求 cdn.example.com,DNS 返回最近的边缘节点 IP(基于 Anycast 或 GeoDNS)
  2. 边缘缓存查找:节点检查本地缓存是否命中
  3. 回源(Origin Pull):若未命中,节点向源站请求资源,缓存后返回给用户
  4. 后续请求:同一区域内其他用户直接从边缘节点获取缓存内容

CDN 的关键性能指标:

  • 缓存命中率:边缘节点直接服务请求的比例。高命中率(> 95%)意味着绝大多数请求无需回源
  • TTFB 降低:边缘节点通常距离用户 < 50ms RTT,相比回源可能降低 200~500ms
  • 带宽卸载:源站只需服务边缘节点的回源请求,而非所有终端用户

边缘计算(Edge Computing)

现代 CDN(Cloudflare Workers、Vercel Edge、AWS Lambda@Edge)支持在边缘节点执行 JavaScript/WASM 代码,实现:

  • 边缘侧 HTML 拼接(ESI)
  • A/B 测试分流
  • 地理位置个性化
  • 认证和鉴权

Brotli 压缩算法

Brotli(2015,Google)是专为 HTTP 内容设计的无损压缩算法,相比 Gzip 有显著的压缩率优势。

压缩率对比(典型文本内容):

| 内容类型 | 原始大小 | Gzip | Brotli | Brotli 优势 | |----------|----------|------|--------|-------------| | JavaScript | 100KB | 35KB | 28KB | -20% | | CSS | 50KB | 12KB | 9KB | -25% | | HTML | 80KB | 18KB | 14KB | -22% | | JSON API | 30KB | 8KB | 6KB | -25% |

Brotli 的优势来自:

  1. 预定义字典:包含常见 Web 内容模式(HTML 标签、CSS 属性、JavaScript 关键字)
  2. 更大的滑动窗口:最大 16MB(Gzip 为 32KB),适合压缩大型资源
  3. 更优的上下文建模:对文本内容的统计特性建模更精确

Brotli 的动态压缩成本:

Brotli 的最高压缩级别(11)比 Gzip 慢 10~20 倍,因此不适合在请求时实时压缩。生产环境的推荐策略是:构建时预压缩静态资源(Brotli 级别 11),动态内容使用 Brotli 级别 4~5 实时压缩。

用法

Nginx HTTP/3 与 Brotli 配置

# nginx.conf - HTTP/3 和 Brotli 配置
server {
    listen 443 quic reuseport;  # HTTP/3 (QUIC)
    listen 443 ssl;             # HTTP/2 fallback

    server_name example.com;

    ssl_certificate     /etc/ssl/certs/example.crt;
    ssl_certificate_key /etc/ssl/private/example.key;
    ssl_protocols       TLSv1.3;  # QUIC 需要 TLS 1.3

    # 启用 0-RTT (Early Data)
    ssl_early_data on;

    # Brotli 预压缩静态资源
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/javascript
                 application/json text/xml application/xml
                 image/svg+xml;

    # 优先使用 Brotli,回退到 Gzip
    location ~* \.(js|css|html|svg)$ {
        # 检查预压缩的 .br 文件
        try_files $uri$ext $uri =404;
    }

    # 静态资源长期缓存
    location /static/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Alt-Svc 'h3=":443"; ma=86400';  # 宣告 HTTP/3 支持
    }
}

构建时预压缩资源(Webpack/Vite)

// webpack.config.js - 使用 compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    // Gzip 预压缩
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8,
    }),
    // Brotli 预压缩
    new CompressionPlugin({
      algorithm: 'brotliCompress',
      test: /\.(js|css|html|svg)$/,
      compressionOptions: { level: 11 }, // 最高压缩率
      threshold: 8192,
      minRatio: 0.8,
      filename: '[path][base].br',
    }),
  ],
};
// vite.config.js - Vite 内置压缩支持
import { defineConfig } from 'vite';
import viteCompression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    viteCompression({
      algorithm: 'brotliCompress',
      ext: '.br',
      threshold: 1024,
    }),
    viteCompression({
      algorithm: 'gzip',
      ext: '.gz',
      threshold: 1024,
    }),
  ],
});

Cloudflare CDN 配置优化

// Cloudflare Workers:边缘 A/B 测试 + 缓存控制
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const cache = caches.default;

    // 检查边缘缓存
    let response = await cache.match(request);
    if (response) {
      return response;
    }

    // A/B 测试:基于用户 ID 分流
    const userId = request.headers.get('Cookie')?.match(/userId=([^;]+)/)?.[1];
    const variant = userId ? (parseInt(userId, 16) % 2 === 0 ? 'A' : 'B') : 'A';

    // 回源请求
    response = await fetch(`https://origin.example.com${url.pathname}?variant=${variant}`, {
      headers: request.headers,
    });

    // 克隆响应以修改头部
    response = new Response(response.body, response);

    // 设置缓存策略
    response.headers.set('Cache-Control', 'public, max-age=3600');
    response.headers.set('Vary', 'Cookie');

    // 存入边缘缓存
    ctx.waitUntil(cache.put(request, response.clone()));

    return response;
  },
};

检测 HTTP/3 支持

// 检查当前连接是否使用 HTTP/3
function checkHTTPVersion() {
  // 通过 Performance API 获取协议信息
  const navEntry = performance.getEntriesByType('navigation')[0];
  if (navEntry) {
    console.log('Protocol:', navEntry.nextHopProtocol); // "h3" 表示 HTTP/3
  }

  // 检查 Alt-Svc 头(服务器宣告支持的协议)
  fetch('/')
    .then(r => console.log('Alt-Svc:', r.headers.get('Alt-Svc')));
}

// 上报网络协议分布
function reportNetworkStats() {
  const navEntry = performance.getEntriesByType('navigation')[0];
  const connection = navigator.connection;

  analytics.track('network_info', {
    protocol: navEntry?.nextHopProtocol || 'unknown',
    effectiveType: connection?.effectiveType, // '4g' | '3g' | '2g' | 'slow-2g'
    downlink: connection?.downlink,
    rtt: connection?.rtt,
    saveData: connection?.saveData,
  });
}

实践

案例:全球站点的 CDN 优化

某 SaaS 平台用户分布在北美、欧洲和亚太。源站位于美国东部,亚太用户 TTFB 高达 800ms。

优化方案:

| 层级 | 措施 | 效果 | |------|------|------| | CDN | 启用 Cloudflare Pro,亚太边缘节点缓存 | TTFB 800ms -> 120ms | | 协议 | 启用 HTTP/3 + 0-RTT | 连接建立时间 150ms -> 0ms(重连) | | 压缩 | Brotli 替代 Gzip | JS/CSS 传输体积 -20% | | 缓存 | 静态资源 Cache-Control: max-age=31536000, immutable | 100% 边缘命中 | | 边缘计算 | Cloudflare Workers 处理地理位置路由 | API 延迟 -40% |

结果:

  • 全球平均 LCP:3.2s -> 1.4s
  • 源站带宽:降低 85%
  • 服务器负载:降低 70%

压缩算法选择矩阵

| 场景 | 推荐算法 | 原因 | |------|----------|------| | 静态资源(构建时) | Brotli level 11 | 最高压缩率,构建时成本可接受 | | 动态 HTML/API | Brotli level 4~5 | 平衡压缩率和 CPU 开销 | | 实时流式内容 | Gzip level 6 | Brotli 流式压缩支持较差 | | 旧版浏览器兼容 | Gzip | Brotli 不支持 IE11 和部分旧版 Android | | 边缘缓存 | Brotli + Gzip 双格式 | 根据 Accept-Encoding 协商 |

陷阱

| 陷阱 | 描述 | 后果 | |------|------|------| | 忽略 TLS 1.3 配置 | HTTP/3 依赖 QUIC,QUIC 依赖 TLS 1.3。服务器未启用 TLS 1.3 则无法支持 HTTP/3 | 浏览器回退到 HTTP/2 或 HTTP/1.1,失去 0-RTT 和流隔离优势 | | Brotli 动态压缩级别过高 | 使用 Brotli level 11 实时压缩动态内容 | 单次压缩耗时 100ms+,TTFB 暴增 | | CDN 缓存策略过于激进 | 对 HTML/API 设置长期缓存,未考虑内容更新 | 用户看到过期内容,数据不一致 | | 忽略 Vary 头 | CDN 缓存未考虑 Accept-EncodingUser-Agent 差异 | Brotli 客户端收到 Gzip 内容,或移动端收到桌面版页面 | | HTTP/3 的 0-RTT 重放攻击 | 0-RTT 数据可能在连接迁移时被重放 | 对非幂等请求(POST)启用 0-RTT 存在安全风险 | | 过度依赖 CDN 默认配置 | 使用 CDN 默认缓存规则,未针对自身业务调优 | 缓存命中率低,回源频繁 | | 未监控 CDN 回源率 | 不了解多少请求实际到达源站 | 源站负载意外增高,成本失控 |

HTTP/3 的兼容性现状

截至 2024 年,HTTP/3 已被 Chrome、Firefox、Safari 支持,但部分企业防火墙和代理可能阻止 UDP 443 端口流量,导致 HTTP/3 握手失败。正确的部署策略是:同时监听 TCP 443(HTTP/2)和 UDP 443(HTTP/3),让浏览器通过 Alt-Svc 头自动协商最佳协议。不要强制仅使用 HTTP/3。

关联章节网络

当前章节
关联章节
交叉引用
前置知识
后续延伸