Nginx-http_gzip_static_module

一、引言:为什么 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 的处理逻辑变为:

  1. 客户端请求 /assets/app.js
  2. Nginx 检查是否存在 /assets/app.js.gz 文件。
  3. 如果存在 且客户端支持 Gzip → 直接返回 .gz 文件内容,并添加 Content-Encoding: gzip 头。
  4. 如果不存在 → 回退到原始文件 /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_staticgzip 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 几乎是"免费"的性能飞跃。


六、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!