HTTP 压缩是 Web 性能优化中投入产出比最高的手段之一。一个合理的压缩配置可以将 JavaScript、CSS 和 HTML 的传输大小减少 60-90%,直接降低页面加载时间和带宽成本。但压缩不是”开启就完了”——算法选择、压缩级别、动态 vs 预压缩、对 CPU 和延迟的影响——每一个决策都有工程权衡。
本文从工程师视角系统对比 gzip、Brotli 和 zstd 三种主流 HTTP 压缩算法,覆盖实际配置、性能测量和选型决策。
一、HTTP 压缩的协商机制
1.1 Accept-Encoding 与 Content-Encoding
HTTP 压缩通过内容协商(Content Negotiation)工作:
# 客户端在请求中声明支持的压缩算法
GET /app.js HTTP/2
Accept-Encoding: gzip, deflate, br, zstd
# br = Brotli, zstd = Zstandard
# 服务端选择一种算法压缩响应,在 Content-Encoding 中声明
HTTP/2 200 OK
Content-Encoding: br
Content-Type: application/javascript
Vary: Accept-Encoding
Content-Length: 45678
# 浏览器根据 Content-Encoding 自动解压
1.2 Content-Encoding vs Transfer-Encoding
这两个头字段容易混淆,但语义完全不同:
| 特性 | Content-Encoding | Transfer-Encoding |
|---|---|---|
| 语义 | 内容的编码(资源本身的属性) | 传输过程的编码 |
| 持久性 | 缓存存储压缩后的数据 | 逐跳(hop-by-hop),缓存前解码 |
| 常见值 | gzip, br, zstd | chunked, gzip |
| HTTP/2 | ✅ 正常使用 | ❌ 不使用(HTTP/2 有自己的帧机制) |
| 用途 | 减小传输体积 | chunked: 流式传输 |
# Content-Encoding: 资源以压缩形式存储和传输
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 12345 # 压缩后的大小
# CDN 缓存的是 gzip 压缩后的版本
# Transfer-Encoding: 仅在传输过程中编码
HTTP/1.1 200 OK
Transfer-Encoding: chunked
# 数据到达后解码,缓存存储原始数据
# HTTP/2 中不使用 Transfer-Encoding: chunked
1.3 Vary: Accept-Encoding 的重要性
# 必须设置 Vary: Accept-Encoding
# 否则缓存可能将 gzip 版本返回给不支持 gzip 的客户端
HTTP/2 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding # 缓存按 Accept-Encoding 区分变体
Cache-Control: public, max-age=86400
# 如果没有 Vary: Accept-Encoding:
# 客户端 A(支持 gzip): 请求 → 收到 gzip 版本 → CDN 缓存
# 客户端 B(不支持 gzip): 请求 → CDN 返回 gzip 版本 → 无法解压!
二、gzip:久经考验的标准
2.1 gzip 基础
gzip 基于 DEFLATE 算法(LZ77 + Huffman 编码),是 Web 压缩的事实标准:
# gzip 压缩级别:1-9
# 级别 1: 最快,压缩率最低
# 级别 6: 默认,速度和压缩率的平衡点
# 级别 9: 最慢,压缩率最高(但比级别 6 只好 2-5%)
# 测试不同级别的效果
for level in 1 3 6 9; do
gzip -c -$level jquery-3.7.1.js > /tmp/jquery.gz
size=$(wc -c < /tmp/jquery.gz)
echo "Level $level: $size bytes"
done
# 典型结果(jQuery 3.7.1, 原始 89 KB):
# Level 1: 33,214 bytes (63% 压缩率)
# Level 3: 31,456 bytes (65% 压缩率)
# Level 6: 29,832 bytes (66% 压缩率)
# Level 9: 29,543 bytes (67% 压缩率)2.2 Nginx gzip 配置
# /etc/nginx/conf.d/gzip.conf
gzip on;
gzip_vary on; # 自动添加 Vary: Accept-Encoding
gzip_proxied any; # 对代理请求也启用压缩
gzip_comp_level 6; # 压缩级别(1-9,推荐 4-6)
gzip_min_length 256; # 小于 256 字节不压缩(压缩可能反而增大)
gzip_buffers 16 8k; # 压缩缓冲区
# 压缩的 MIME 类型
gzip_types
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/xml
application/xml+rss
application/atom+xml
application/xhtml+xml
application/x-javascript
application/wasm
image/svg+xml
font/opentype
font/ttf
font/eot;
# 注意:不要压缩图片(JPEG/PNG/WebP 已经是压缩格式)
# 不要压缩视频(MP4/WebM 已经是压缩格式)
# 不要压缩已经压缩的文件(.gz、.br、.zip)
# 预压缩:使用预先生成的 .gz 文件(避免动态压缩的 CPU 开销)
gzip_static on;
# 当请求 /style.css 且存在 /style.css.gz 时,直接返回 .gz 文件
# 可以使用更高压缩级别预先压缩(如 gzip -9)
2.3 gzip 的局限性
- 压缩率已到极限:DEFLATE 算法(1993 年)的搜索窗口只有 32 KB,对现代的大文件(数百 KB 的 JavaScript)压缩效率不够
- 无法用于 HTTPS 的请求体压缩:BREACH 攻击利用压缩后大小的变化泄露 CSRF Token 等敏感信息
- 不支持字典压缩:每个资源独立压缩,无法利用资源间的重复内容
三、Brotli:Google 的现代替代
3.1 Brotli 特性
Brotli(2015 年)使用更大的搜索窗口(最大 16 MB)和内置的 Web 常见字符串字典:
# Brotli 压缩级别:0-11
# 级别 0-4: 速度优先(适合动态压缩)
# 级别 5-9: 平衡
# 级别 10-11: 压缩率优先(适合预压缩,非常慢)
# 安装 brotli 命令行工具
# Ubuntu: apt install brotli
# macOS: brew install brotli
# 测试不同级别
for level in 1 4 6 9 11; do
brotli -c -q $level jquery-3.7.1.js > /tmp/jquery.br
size=$(wc -c < /tmp/jquery.br)
echo "Level $level: $size bytes"
done
# 典型结果(jQuery 3.7.1, 原始 89 KB):
# Level 1: 31,024 bytes (65% 压缩率)
# Level 4: 27,891 bytes (69% 压缩率)
# Level 6: 26,234 bytes (71% 压缩率)
# Level 9: 25,012 bytes (72% 压缩率)
# Level 11: 24,456 bytes (73% 压缩率) ← 比 gzip -9 小 17%3.2 Brotli 的内置字典
Brotli 相比 gzip 的独特优势是内置了一个 ~120 KB 的静态字典,包含 Web 上常见的 HTML/CSS/JavaScript 字符串:
字典包含的内容示例:
- HTML 标签: <html, <head, <body, <div, <script, </script>
- CSS 属性: background-color, font-family, text-decoration
- JavaScript 关键字: function, return, undefined, prototype
- HTTP 头字段: Content-Type, Accept-Encoding, Cache-Control
- 常见 URL 片段: http://, https://, www., .com, .org
这意味着对于 Web 资源,Brotli 从"第一个字节"就能引用字典中的
常见字符串,而 gzip 需要先在数据中发现重复模式才能压缩。
这解释了为什么 Brotli 对小文件(<10 KB)的压缩优势特别明显。
3.3 Nginx Brotli 配置
# 需要安装 ngx_brotli 模块
# 编译安装或使用发行版包管理器
# /etc/nginx/conf.d/brotli.conf
brotli on;
brotli_comp_level 6; # 动态压缩级别(0-11,推荐 4-6)
brotli_min_length 256;
brotli_types
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/xml
application/wasm
image/svg+xml
font/opentype
font/ttf;
# 预压缩(使用预先生成的 .br 文件)
brotli_static on;
# 当请求 /app.js 且存在 /app.js.br 时,直接返回 .br 文件
3.4 Brotli 的限制
- 仅支持 HTTPS:浏览器只在 HTTPS 连接上协商 Brotli(防止中间设备干扰)
- 高压缩级别很慢:Level 11 比 gzip -9 慢 10-50 倍,只适合预压缩
- 解压比 gzip 略慢:但差距通常可以忽略(~5%)
- 服务端支持不如 gzip 广泛:某些老旧的代理/CDN 可能不支持
四、zstd:Facebook 的新挑战者
4.1 zstd 特性
Zstandard(zstd,2016 年)由 Facebook 开发,在速度和压缩率之间取得了新的平衡:
# zstd 压缩级别:1-22(默认 3)
# 级别 1-3: 极快,压缩率与 gzip -6 相当
# 级别 4-9: 平衡
# 级别 10-19: 高压缩率
# 级别 20-22: 极高压缩率(非常慢)
# 安装 zstd
# Ubuntu: apt install zstd
# 测试不同级别
for level in 1 3 6 9 15 19; do
zstd -c -$level jquery-3.7.1.js > /tmp/jquery.zst
size=$(wc -c < /tmp/jquery.zst)
echo "Level $level: $size bytes"
done
# 典型结果(jQuery 3.7.1, 原始 89 KB):
# Level 1: 30,567 bytes (66% 压缩率) ← 速度是 gzip -6 的 3-5 倍
# Level 3: 28,234 bytes (68% 压缩率)
# Level 6: 27,012 bytes (70% 压缩率)
# Level 9: 26,345 bytes (70% 压缩率)
# Level 15: 25,678 bytes (71% 压缩率)
# Level 19: 24,890 bytes (72% 压缩率)4.2 zstd 的字典压缩
zstd 支持训练自定义字典(Dictionary Compression),这是它在 HTTP 压缩中的独特优势:
# 训练字典:从一批相似的文件中学习共同模式
# 适用于 API 响应(结构相似的 JSON)
# 收集训练样本(100+ 个相似的 JSON 响应)
mkdir -p /tmp/samples
for i in $(seq 1 100); do
curl -s "https://api.example.com/users/$i" > /tmp/samples/user_$i.json
done
# 训练字典
zstd --train /tmp/samples/*.json -o /tmp/api_dict.zst --maxdict=32768
# 字典大小通常 16-128 KB
# 使用字典压缩
zstd -c -D /tmp/api_dict.zst /tmp/samples/user_1.json > /tmp/compressed.zst
# 对于小的 JSON 响应(<1 KB),字典压缩可以比普通 zstd 多压缩 50-80%4.3 zstd 在 HTTP 中的支持现状
浏览器支持(截至 2025 年):
Chrome 123+: ✅ 支持 Content-Encoding: zstd
Firefox 126+: ✅ 支持
Safari 18+: ✅ 支持
Edge 123+: ✅ 支持
服务端支持:
Nginx: ⚠️ 需要第三方模块(ngx_zstd)
Caddy: ✅ 原生支持
Apache: ⚠️ 需要 mod_zstd
CDN:
Cloudflare: ✅ 支持
Fastly: ✅ 支持
AWS CloudFront: ⚠️ 部分支持
RFC 8878: Zstandard Compression and the 'application/zstd' Media Type
RFC 9110/9112: zstd 作为 Content-Encoding 值
4.4 zstd 与 Shared Dictionary Compression
zstd 在 HTTP 场景中的一个前沿应用是共享字典压缩(Shared Dictionary Compression for HTTP),Chrome 已在实验中支持:
传统压缩:每个资源独立压缩
app.v1.js (100 KB) → gzip → 30 KB
app.v2.js (101 KB) → gzip → 31 KB (虽然只改了 1 KB)
共享字典压缩:
1. 浏览器已缓存 app.v1.js
2. 服务端用 app.v1.js 作为字典压缩 app.v2.js
3. 压缩后只有 delta 部分:~2 KB!(而非 31 KB)
工作流程:
浏览器: 我已有 app.v1.js (hash: abc123)
Available-Dictionary: :abc123:
服务端: 用 abc123 作为字典压缩 app.v2.js
Content-Encoding: dcz # Dictionary Compressed Zstandard
浏览器: 用缓存的 app.v1.js 作为字典解压
适用场景:
- JavaScript 增量更新(版本间差异通常 < 5%)
- CSS 框架更新
- API 响应中的共同结构
五、三种算法的工程对比
5.1 浏览器支持对比
| 浏览器 | gzip | Brotli (br) | zstd | 备注 |
|---|---|---|---|---|
| Chrome | 所有版本 | 50+ (2016) | 123+ (2024) | |
| Firefox | 所有版本 | 44+ (2016) | 126+ (2024) | |
| Safari | 所有版本 | 11+ (2017) | 18+ (2024) | |
| Edge | 所有版本 | 15+ (2017) | 123+ (2024) | |
| IE 11 | ✅ | ❌ | ❌ | IE 不支持 br/zstd |
| curl | ✅ | 7.57+ | 7.89+ | 需要编译时链接对应库 |
| Node.js | ✅ | 10.16+ | 实验性 | zlib/brotli 内置 |
5.2 压缩率对比
以典型的 Web 资源为例,在默认压缩级别下的对比(原始大小 → 压缩后大小):
| 资源 | 原始 | gzip -6 | br -6 | zstd -6 | br vs gzip |
|---|---|---|---|---|---|
| jQuery 3.7.1 (JS) | 89 KB | 30 KB | 26 KB | 27 KB | -13% |
| Bootstrap 5.3 (CSS) | 227 KB | 33 KB | 26 KB | 29 KB | -21% |
| React 18 (JS) | 142 KB | 46 KB | 38 KB | 41 KB | -17% |
| 大型 JSON API | 500 KB | 45 KB | 35 KB | 38 KB | -22% |
| HTML 文档 | 50 KB | 12 KB | 9 KB | 10 KB | -25% |
5.2 速度对比
# 使用 hyperfine 进行基准测试
# 测试文件:Bootstrap 5.3 CSS (227 KB)
# 压缩速度(MB/s,越高越好)
hyperfine \
'gzip -6 -c bootstrap.css > /dev/null' \
'brotli -q 6 -c bootstrap.css > /dev/null' \
'zstd -6 -c bootstrap.css > /dev/null'
# 典型结果:
# gzip -6: ~50 MB/s
# brotli -6: ~30 MB/s
# zstd -6: ~150 MB/s ← zstd 在同等压缩率下速度最快
# 解压速度(MB/s,越高越好)
# gzip: ~300 MB/s
# brotli: ~400 MB/s
# zstd: ~800 MB/s ← zstd 解压最快5.3 CPU 开销对比
动态压缩时的 CPU 开销(相对于不压缩的额外 CPU 时间):
请求量: 10000 req/s,每个响应 50 KB
gzip -6: +15% CPU
brotli -6: +25% CPU
zstd -6: +8% CPU ← zstd CPU 开销最低
brotli -11: +300% CPU ← 仅适合预压缩!
zstd -19: +200% CPU ← 仅适合预压缩!
结论:
- 动态压缩(实时): zstd -3 或 gzip -4 CPU 开销最低
- 预压缩(构建时): brotli -11 压缩率最高
5.4 决策矩阵
gzip Brotli zstd
浏览器支持 100% 97%+ 90%+
压缩率 ★★★ ★★★★★ ★★★★
压缩速度 ★★★ ★★ ★★★★★
解压速度 ★★★ ★★★★ ★★★★★
CPU 开销 ★★★ ★★ ★★★★★
生态成熟度 ★★★★★ ★★★★ ★★★
字典压缩 ❌ ✅(内置) ✅(自定义)
仅 HTTPS ❌ ✅ ❌
推荐策略:
1. 静态资源预压缩: Brotli -11(压缩率最高)
2. 动态 API 压缩: zstd -3 或 gzip -4(CPU 开销低)
3. 不支持 HTTPS: gzip(Brotli 需要 HTTPS)
4. 通用降级方案: 先尝试 br → 然后 zstd → 最后 gzip
六、动态压缩 vs 预压缩
6.1 动态压缩
请求 → Nginx → 实时压缩 → 响应
优点:
- 配置简单,一次设置覆盖所有资源
- 总是返回最新内容的压缩版本
- 不需要额外的构建步骤
缺点:
- 每次请求都消耗 CPU
- 不能使用高压缩级别(太慢)
- 首次请求有压缩延迟
6.2 预压缩
构建时 → 预先生成 .gz / .br / .zst 文件
请求时 → Nginx 直接返回预压缩文件
构建脚本:
#!/bin/bash
# precompress.sh - 预压缩静态资源
DIST_DIR="./dist"
find "$DIST_DIR" -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \
-o -name "*.json" -o -name "*.svg" -o -name "*.xml" -o -name "*.wasm" \) | \
while read file; do
# 跳过小文件
size=$(wc -c < "$file")
if [ "$size" -lt 256 ]; then
continue
fi
# gzip -9(兼容性最好)
gzip -9 -k -f "$file"
# Brotli -11(压缩率最高,速度最慢但只在构建时运行一次)
brotli -q 11 -k -f "$file"
# zstd -19(高压缩率)
zstd -19 -k -f "$file" -o "${file}.zst" 2>/dev/null
echo "Compressed: $file"
echo " Original: $size bytes"
echo " gzip: $(wc -c < "${file}.gz") bytes"
echo " brotli: $(wc -c < "${file}.br") bytes"
echo " zstd: $(wc -c < "${file}.zst") bytes"
done# Nginx 预压缩配置
# 优先返回 Brotli,降级到 gzip
brotli_static on; # 检查 .br 文件
gzip_static on; # 检查 .gz 文件
# 处理顺序:
# 1. 如果客户端支持 br 且存在 .br 文件 → 返回 .br
# 2. 如果客户端支持 gzip 且存在 .gz 文件 → 返回 .gz
# 3. 返回原始文件
6.3 混合策略
# 生产环境推荐:静态预压缩 + 动态 gzip 兜底
# 静态资源:预压缩
location /assets/ {
brotli_static on;
gzip_static on;
# 不开启动态压缩(预压缩文件已存在)
}
# 动态内容(API 响应):实时压缩
location /api/ {
gzip on;
gzip_comp_level 4; # 低级别减少 CPU
gzip_types application/json;
# 如果安装了 zstd 模块,优先使用 zstd
# zstd on;
# zstd_comp_level 3;
proxy_pass http://backend;
}
七、压缩级别的工程权衡
7.1 动态压缩的最优级别
目标:找到"边际收益递减"的拐点
gzip: 级别 4-6
从 1 到 4:压缩率提升 ~8%,速度下降 ~30%
从 4 到 6:压缩率提升 ~3%,速度下降 ~40%
从 6 到 9:压缩率提升 ~2%,速度下降 ~60%
→ 推荐级别 4(CPU 敏感)或 6(默认平衡)
Brotli: 级别 4-6
从 1 到 4:压缩率提升 ~10%,速度下降 ~50%
从 4 到 6:压缩率提升 ~5%,速度下降 ~40%
从 6 到 9:压缩率提升 ~3%,速度下降 ~80%
从 9 到 11:压缩率提升 ~2%,速度下降 ~500%(!)
→ 动态压缩推荐 4-6,预压缩推荐 11
zstd: 级别 1-6
从 1 到 3:压缩率提升 ~5%,速度下降 ~30%
从 3 to 6:压缩率提升 ~3%,速度下降 ~50%
从 6 to 9:压缩率提升 ~2%,速度下降 ~60%
→ 动态压缩推荐 1-3(速度优先)或 3-6(平衡)
7.2 压缩与 TTFB 的关系
压缩增加了 TTFB(Time to First Byte)但减少了传输时间。
不压缩:
TTFB: 50ms | 传输: 100ms(100KB @ 1Mbps)| 总计: 150ms
gzip -6:
TTFB: 55ms | 传输: 30ms(30KB @ 1Mbps) | 总计: 85ms ← 快 43%
brotli -6:
TTFB: 60ms | 传输: 25ms(25KB @ 1Mbps) | 总计: 85ms ← 快 43%
brotli -11:
TTFB: 200ms | 传输: 23ms(23KB @ 1Mbps) | 总计: 223ms ← 慢 49%!
结论:高级别动态压缩会增加 TTFB,抵消传输时间的收益。
对于延迟敏感的场景(API 响应),应该使用低压缩级别。
八、不应该压缩的内容
8.1 已压缩的格式
以下格式已经是压缩格式,再次压缩几乎没有效果,反而浪费 CPU:
图片: JPEG, PNG, WebP, AVIF, GIF
视频: MP4, WebM, AV1
音频: MP3, AAC, Opus, FLAC
压缩包: ZIP, TAR.GZ, RAR, 7Z
字体: WOFF2(内置 Brotli 压缩)
例外:
SVG: 可以压缩(SVG 是 XML 文本格式)
BMP: 可以压缩(BMP 是无压缩格式)
WOFF(1): 可以压缩(WOFF1 使用 zlib,但不如 WOFF2 的 Brotli)
TTF/OTF: 可以压缩(原始字体格式,未压缩)
8.2 极小的响应
# 小于 150-256 字节的响应不应该压缩
gzip_min_length 256;
brotli_min_length 256;
# 原因:
# 1. gzip 头部本身约 20 字节,对极小响应压缩可能反而增大
# 2. 压缩的 CPU 开销相对于节省的字节数不划算
# 3. 典型的小响应(如 204 No Content、301 重定向)不值得压缩
8.3 BREACH 攻击与动态内容压缩
BREACH 攻击(Browser Reconnaissance and Exfiltration via Adaptive
Compression of Hypertext)利用 HTTP 压缩来泄露敏感信息。
攻击条件:
1. 响应使用 HTTP 压缩
2. 响应中包含用户可控的输入和敏感数据(如 CSRF Token)
3. 攻击者可以诱导用户发起请求并观察响应大小
攻击原理:
响应包含: <input name="csrf" value="SECRET123">
攻击者注入: ?search=SECRET1
如果 SECRET1 出现在 CSRF Token 中,
压缩后的响应会更小(因为 SECRET1 在响应中重复出现)
攻击者逐字符猜测,最终还原完整的 CSRF Token
缓解措施:
1. CSRF Token 使用 masking(每次响应不同的表示形式)
2. 对敏感响应添加随机 padding
3. 使用 SameSite Cookie 防御 CSRF(从源头解决)
4. 不在 URL 参数中回显用户输入
九、CDN 压缩策略
9.1 CDN 压缩行为
CDN 的压缩策略有两种模式:
模式 1: CDN 边缘压缩
源站 → 未压缩响应 → CDN → 压缩 → 用户
优点: 源站 CPU 不参与压缩
缺点: CDN 边缘每次缓存命中都要压缩(除非缓存压缩版本)
模式 2: 源站压缩 + CDN 缓存
源站 → 压缩响应 → CDN 缓存压缩版本 → 用户
优点: CDN 直接返回,无需再压缩
缺点: 每种压缩格式需要独立的缓存条目(需要 Vary: Accept-Encoding)
# 检查 CDN 是否压缩响应
curl -sI -H "Accept-Encoding: gzip" https://example.com/style.css | \
grep -iE "content-encoding|cf-cache"
# Content-Encoding: gzip
# CF-Cache-Status: HIT
# 检查 CDN 是否支持 Brotli
curl -sI -H "Accept-Encoding: br" https://example.com/style.css | \
grep -i content-encoding
# Content-Encoding: br
# 对比不同压缩算法的响应大小
for enc in gzip br zstd identity; do
size=$(curl -s -H "Accept-Encoding: $enc" -o /dev/null -w "%{size_download}" \
https://example.com/style.css)
echo "$enc: $size bytes"
done9.2 Cloudflare 压缩配置
Cloudflare 的压缩行为:
1. 默认对所有文本类型(HTML/CSS/JS/JSON/XML/SVG)启用压缩
2. 支持 gzip 和 Brotli(自动根据 Accept-Encoding 选择)
3. Brotli 级别:对缓存内容使用高压缩级别(类似预压缩)
4. zstd: 2024 年开始支持
注意事项:
- 如果源站已经返回压缩响应,Cloudflare 不会重新压缩
- 如果源站返回 Content-Encoding: gzip,但客户端请求 br,
Cloudflare 可能解压再用 Brotli 重新压缩
十、压缩效果监控
10.1 测量压缩率
# 使用 curl 测量实际的压缩效果
# 获取压缩后大小
compressed=$(curl -s -H "Accept-Encoding: gzip" -o /dev/null \
-w "%{size_download}" https://example.com/app.js)
# 获取原始大小
original=$(curl -s -H "Accept-Encoding: identity" -o /dev/null \
-w "%{size_download}" https://example.com/app.js)
# 计算压缩率
ratio=$(echo "scale=2; (1 - $compressed / $original) * 100" | bc)
echo "Compression ratio: ${ratio}%"
echo "Original: $original bytes → Compressed: $compressed bytes"
echo "Saved: $(($original - $compressed)) bytes"10.2 压缩监控指标
关键监控指标:
1. 压缩率(Compression Ratio)
目标: 文本资源 > 60%,JSON API > 70%
告警: 压缩率 < 40% 可能是配置问题
2. 压缩覆盖率
目标: 100% 的文本响应都应该被压缩
检查: Content-Encoding 头是否存在
3. CPU 使用率变化
监控: 启用压缩前后的 CPU 使用率
如果 CPU 增加 > 20%,考虑降低压缩级别或使用预压缩
4. TTFB 变化
监控: 压缩对首字节时间的影响
如果 TTFB 增加 > 50ms,考虑降低压缩级别
10.3 常见压缩问题排查
# 问题 1: 响应没有被压缩
curl -sI -H "Accept-Encoding: gzip" https://example.com/api/data | \
grep -i content-encoding
# 如果没有 Content-Encoding 头:
# 检查清单:
# 1. Nginx 是否开启了 gzip on?
# 2. 响应 Content-Type 是否在 gzip_types 列表中?
# 3. 响应大小是否超过 gzip_min_length?
# 4. 上游是否已经发送了 Content-Encoding?
# 5. 是否有 proxy_set_header Accept-Encoding "" 禁用了压缩?
# 问题 2: 压缩后文件反而变大
# 原因: 文件太小或已经是压缩格式
# 解决: 设置 gzip_min_length 256 并排除已压缩格式
# 问题 3: Brotli 在 HTTP 上不生效
# 原因: 浏览器只在 HTTPS 上协商 Brotli
# Accept-Encoding 中不包含 br
# 解决: 确保使用 HTTPS
# 问题 4: ETag 在压缩后不匹配
# Nginx 动态 gzip 会将强 ETag 转为弱 ETag
# 这可能影响条件请求的行为
# 解决: 使用 gzip_static on 或确保 ETag 基于压缩后内容
# 问题 5: 代理/CDN 不转发 Accept-Encoding
# 某些代理会去掉 Accept-Encoding 头
# 导致源站不知道客户端支持什么压缩算法
# 解决: 检查代理配置,确保转发 Accept-Encoding
# Nginx: proxy_set_header Accept-Encoding $http_accept_encoding;十一、总结
HTTP 压缩的工程选型可以总结为以下几个核心原则:
预压缩用 Brotli -11,动态压缩用 gzip -4 或 zstd -3。预压缩只在构建时运行一次,可以承受高压缩级别的 CPU 开销。动态压缩在每次请求时运行,必须控制延迟和 CPU。
不要用高级别做动态压缩。Brotli -11 比 -6 慢 5-10 倍,但只多压缩 2-3%。这个权衡在动态场景中不值得。
zstd 是新的最佳选择——如果你的客户端支持。zstd 在同等压缩率下速度是 gzip 的 3-5 倍,CPU 开销是 Brotli 的 1/3。但浏览器支持率还不如 gzip 和 Brotli。
始终设置 Vary: Accept-Encoding。否则 CDN 可能将 gzip 响应返回给不支持 gzip 的客户端,或者将 identity 响应返回给支持 Brotli 的客户端。
不要压缩已经压缩的格式。JPEG、PNG、WOFF2、MP4 不需要 HTTP 压缩。压缩它们只会浪费 CPU。
监控压缩效果。定期检查压缩率、TTFB 变化和 CPU 使用率。压缩配置不是”设完就忘”的——网站内容结构变化可能影响压缩效果。
关注共享字典压缩的发展。Shared Dictionary Compression for HTTP(Chrome 实验中)可能彻底改变 Web 资源的增量更新方式——将 JavaScript 更新的传输大小从数十 KB 降低到几 KB。这对于频繁迭代的 SPA 应用来说意义重大。
最后一个实践建议:在优化压缩之前,先确认你的服务是否已经启用了压缩。现实中”以为启用了压缩但其实没有”的情况出乎意料地常见。用一条简单的 curl 命令就能验证:
curl -sI -H "Accept-Encoding: gzip" https://your-site.com/ | grep -i content-encoding
# 如果没有输出 Content-Encoding: gzip,说明压缩未生效参考文献
- RFC 9110: HTTP Semantics (Content-Encoding)
- RFC 7932: Brotli Compressed Data Format
- RFC 8878: Zstandard Compression and the ‘application/zstd’ Media Type
- Google: Introduction to Brotli Compression
- Facebook: Zstandard - Fast real-time compression algorithm
上一篇:Cookie 与 Session 工程:安全属性与现代限制
下一篇:HTTP 安全头完整实战:CORS、CSP、HSTS
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
网络工程索引
汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。
【网络工程】QUIC 生态与工程部署:从实验到生产
QUIC 已经不是实验性协议——HTTP/3 标准化后,CDN、浏览器和主流服务端框架都在推进 QUIC 支持。本文从工程视角对比主流 QUIC 库的成熟度和性能特征,讲解 CDN/负载均衡器的 QUIC 适配方案、从 TCP 迁移到 QUIC 的渐进路径、QUIC 调试工具链,以及生产环境的部署陷阱和性能调优实践。
【网络工程】eBPF 可编程网络:从包过滤到流量工程
eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。
【网络工程】可编程数据平面与 P4:软件定义转发
传统网络设备的转发逻辑固化在硬件中。P4 语言让交换机的转发管线可编程——你可以定义自己的包头解析、匹配规则和转发动作。本文从 P4 语言核心概念出发,讲解 Parser/Match-Action/Deparser 的编程模型、可编程交换机芯片(Tofino)的架构、P4 在数据中心和运营商网络中的应用案例,以及 P4 与 eBPF 的定位差异。