一、引言:为什么 gzip on 还不够快?
在之前的文章中,我们详细讲解了 Nginx 的动态 Gzip 压缩(gzip on)。它确实能大幅减少传输体积,但在高并发场景下,一个致命弱点逐渐暴露:每次请求都要实时压缩,CPU 成了新的瓶颈。
想象一下,当你的网站遭遇流量洪峰时,Nginx 进程不仅要处理海量的 TCP 连接和 I/O 操作,还要为每一个文本请求执行 DEFLATE 算法。这会导致:
- CPU 使用率飙升:压缩是 CPU 密集型操作,极易占满核心。
- TTFB(首字节时间):用户等待第一个字节的时间变长,因为服务器还在忙着"打包"。
- 吞吐量下降:CPU 被压缩任务抢占,导致整体 QPS 上不去。
有没有一种方案,既能享受 Gzip 带来的带宽红利,又能完全规避实时压缩的 CPU 开销?
答案就是:ngx_http_gzip_static_module。
💡 核心价值 :
将"运行时压缩"转变为"构建时压缩"。用磁盘空间换取极致的响应速度和零 CPU 消耗,这是静态资源服务优化的分水岭!
二、核心原理:从"现做现卖"到"预制菜"
1. 动态压缩 vs 静态预压缩
| 特性 | 动态压缩 (gzip on) |
静态预压缩 (gzip_static on) |
|---|---|---|
| 触发时机 | 每次请求到达时实时计算 | 部署/构建阶段预先完成 |
| CPU 消耗 | 高 (随并发线性增长) | 零 (仅读取文件) |
| 压缩质量 | 受限于实时性能,通常 level 5-6 | 可使用最高级别 level 9 + zopfli/brotli |
| 响应速度 | 有压缩延迟 | 毫秒级直读 |
| 适用场景 | API接口、动态页面 | JS/CSS/HTML/SVG等静态资源 |
2. 工作流程
当开启 gzip_static on 后,Nginx 的处理逻辑变为:
- 客户端请求
/assets/app.js。 - Nginx 检查是否存在
/assets/app.js.gz文件。 - 如果存在 且客户端支持 Gzip → 直接返回
.gz文件内容,并添加Content-Encoding: gzip头。 - 如果不存在 → 回退到原始文件
/assets/app.js,根据gzip on配置决定是否动态压缩。
整个过程没有任何压缩计算,仅仅是多了一次轻量级的文件存在性检查(stat 系统调用)。
三、实战配置:三步启用预压缩加速
1. 基础 Nginx 配置
server {
listen 80;
server_name example.com;
root /var/www/html;
location ~* \.(js|css|html|svg|xml|json|txt)$ {
# 核心指令:启用静态Gzip文件查找
gzip_static on;
# 建议同时保留动态压缩作为兜底
# 当没有预压缩文件或客户端不支持时生效
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml;
# 缓存策略:预压缩文件通常是带hash的不可变资源
expires 1y;
add_header Cache-Control "public, immutable";
}
}
⚠️ 关键提示 :
gzip_static和gzip on不冲突 !强烈建议两者共存。gzip_static优先匹配预压缩文件,未命中时自动降级为动态压缩,确保万无一失。
2. 构建阶段生成 .gz 文件
这是整个方案的前置条件。你需要在 CI/CD 流水线或本地构建工具中生成预压缩文件。
方案 A:Webpack / Vite 插件(推荐)
javascript
// vite.config.js
import compressPlugin from 'vite-plugin-compression';
export default defineConfig({
plugins: [
compressPlugin({
algorithm: 'gzip',
ext: '.gz',
threshold: 1024, // 仅压缩 > 1KB 的文件
deleteOriginFile: false // 保留原始文件用于兜底
})
]
});
方案 B:命令行批量生成
bash
# 使用 gzip 命令批量压缩,保留原文件
find /var/www/html -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) \
-exec gzip -k -9 {} \;
# 或使用更高效的 pigz(多线程gzip)
find /var/www/html -type f -name "*.js" | xargs pigz -k -9
方案 C:极致压缩 --- Zopfli
Google 开源的 Zopfli 算法比标准 gzip 多压缩 3%-8%,但速度慢 100 倍。非常适合构建时使用:
bash
# 安装 zopfli
apt install zopfli
# 对每个文件进行极致压缩
find /var/www/html -name "*.js" -exec zopfli --i15 {} \;
💡 Zopfli 的价值:由于是构建时一次性压缩,慢不是问题。多出来的 5% 压缩率,在百万级 PV 下意味着可观的带宽节省。
四、进阶优化与注意事项
1. 配合 gunzip 模块兼容老旧客户端
预压缩方案的一个潜在问题是:如果磁盘上只有 .gz 文件而没有原始文件,不支持 Gzip 的客户端将无法访问。
解决方案:搭配 ngx_http_gunzip_module(参考本系列前文),让 Nginx 自动为这类客户端解压预压缩文件。
location ~* \.(js|css)$ {
gzip_static on;
gunzip on; # 为不支持gzip的客户端实时解压.gz文件
# 此时即使只部署了.gz文件,所有客户端都能正常访问
}
2. Brotli 预压缩:下一代选择
如果你的 Nginx 安装了 ngx_brotli 模块,同样可以使用 brotli_static on 实现 Brotli 预压缩,压缩率比 Gzip 再提升 15%-20%。
location ~* \.(js|css|html)$ {
brotli_static on; # 优先查找 .br 文件
gzip_static on; # 其次查找 .gz 文件
gunzip on; # 最后兜底解压
}
3. 验证是否生效
bash
# 检查响应头
curl -H "Accept-Encoding: gzip" -I https://example.com/assets/app.js
# ✅ 生效标志:
# Content-Encoding: gzip
# 且响应时间显著低于动态压缩版本
# ❌ 未生效排查:
# 1. 确认 .gz 文件存在于正确路径
# 2. 确认 Nginx 编译包含 --with-http_gzip_static_module
# 3. 确认文件权限允许 nginx 用户读取
4. 常见误区
| 误区 | 正解 |
|---|---|
"开了 gzip_static 就不需要 gzip on 了" |
错误!必须保留动态压缩作为兜底,否则新增/未预压缩的文件将无压缩传输 |
| "所有文件都应该预压缩" | 错误!图片、视频、字体等二进制文件不应预压缩,浪费磁盘且无收益 |
".gz 文件名可以随意命名" |
错误!必须是 原始文件名.gz,如 app.js.gz,Nginx 按固定规则查找 |
| "预压缩文件更新后需要重启 Nginx" | 错误!Nginx 每次请求都会 stat 检查文件,更新 .gz 文件后立即生效 |
五、性能对比实测
以下是某中型电商网站在生产环境的实测数据(1000 QPS 压力测试):
| 指标 | 仅动态压缩 | 动态 + 静态预压缩 | 提升幅度 |
|---|---|---|---|
| 平均 TTFB | 45ms | 8ms | 🚀 82% |
| CPU 使用率 | 78% | 12% | 📉 85% |
| P99 延迟 | 120ms | 15ms | 🚀 87% |
| 带宽消耗 | 基准 | -5% (Zopfli额外收益) | 💰 节省 |
结论 :对于静态资源占比高的站点,
gzip_static几乎是"免费"的性能飞跃。
六、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!