Gzip 深度解析:算法原理、压缩级别选择、在 Nginx/Apache 中的配置、动态 vs 静态压缩

Gzip 这东西,很多同学觉得就是个开关——打开就完事了。但实际项目中,我见过太多因为 Gzip 配置不当导致的性能事故。要么压缩率太低白费功夫,要么 CPU 被打满响应变慢。今天咱们就把 Gzip 从头到尾捋一遍。

Gzip 压缩算法原理

Gzip 的核心其实是两个步骤的组合:LZ77 算法 + Huffman 编码。说白了,就是先找重复,再给重复的内容编个更短的码。

LZ77 做了什么?
它用一个滑动窗口扫描数据。发现重复的字符串时,不存原文,只存一个指针——指向之前出现的位置和长度。比如「ababab」这种模式,压缩后就是一个很小的引用。

Huffman 编码呢?
它给高频字符分配短编码,低频字符分配长编码。你想想看,一个 HTML 文件里空格和换行符出现频率极高,用短编码表示它们,体积自然就下来了。

我在项目中遇到过一个问题:一个 JSON 接口返回的数据里有很多重复的字段名,比如 "user_name" 出现了上千次。LZ77 能识别这种重复,但前提是重复距离在滑动窗口范围内。默认窗口大小是 32KB,如果你的重复模式跨过了这个窗口,压缩率就会明显下降。

核心结论:Gzip 的压缩率取决于数据的重复度和窗口大小。文本类资源(HTML、CSS、JS、JSON)压缩效果最好,图片和视频基本压不动。

压缩级别选择:1-9 到底怎么选?

Gzip 提供了 1 到 9 共 9 个压缩级别。1 最快但压缩率最低,9 最慢但压缩率最高。很多人上来就设 9,觉得「压缩率越高越好」。嗯,这里要注意——压缩级别不是线性增长的。

级别 压缩比(典型值) 压缩速度 适用场景
1 约 60% 极快 实时压缩、高并发 API
2-3 约 55%-50% 快 动态压缩、中等流量
4-6 约 45%-40% 中等 静态压缩、预生成文件
7-9 约 38%-35% 慢 离线打包、发布包

我个人习惯是:动态压缩用级别 2-3,静态压缩用级别 6。为什么?因为从 6 到 9,压缩率提升不到 5%,但压缩时间可能翻倍。你想想看,如果用户请求一个 100KB 的 JS 文件,级别 6 压缩到 40KB,级别 9 压缩到 38KB——省了 2KB,但服务器多花了 3 倍 CPU。这笔账不划算。

我的经验:对于 Nginx 动态压缩,我通常设 gzip_comp_level 3。这个级别在压缩率和 CPU 开销之间取得了很好的平衡。如果你用 CDN 预压缩,可以设到 6。

在 Nginx 中的配置

Nginx 的 Gzip 配置其实很灵活。我直接给出一份我常用的配置模板,每行都加了注释。

# 开启 Gzip
gzip on;

# 设置压缩级别(1-9),推荐 3
gzip_comp_level 3;

# 最小压缩阈值,小于 1KB 的文件不压缩
gzip_min_length 1024;

# 设置压缩缓冲区
gzip_buffers 16 8k;

# 设置 HTTP 版本
gzip_http_version 1.1;

# 设置压缩类型
gzip_types text/plain text/css application/json application/javascript
           text/xml application/xml application/xml+rss text/javascript
           image/svg+xml;

# 禁用对 IE6 的压缩(老版本有 bug)
gzip_disable "msie6";

# 添加 Vary 头,告诉缓存服务器根据 Accept-Encoding 区分缓存
gzip_vary on;

我曾经踩过一个坑:gzip_types 里漏掉了 application/json。结果 API 返回的 JSON 数据全是明文传输,白白浪费带宽。排查了半天才发现是类型没配全。

注意:gzip_types 默认只包含 text/html。其他类型必须手动添加。常见的遗漏包括:application/json、application/javascript、image/svg+xml。

在 Apache 中的配置

Apache 使用 mod_deflate 模块实现 Gzip。配置思路和 Nginx 类似,但语法不同。

# 开启 mod_deflate
AddOutputFilterByType DEFLATE text/html text/plain text/css
AddOutputFilterByType DEFLATE application/json application/javascript
AddOutputFilterByType DEFLATE text/xml application/xml

# 设置压缩级别(1-9),推荐 3
DeflateCompressionLevel 3

# 设置最小压缩大小(字节)
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary

# 添加 Vary 头
Header append Vary Accept-Encoding

Apache 的配置有个细节:SetEnvIfNoCase 用来排除图片等已经压缩过的资源。虽然 Gzip 对图片基本无效,但加上这个规则可以避免不必要的 CPU 消耗。

动态压缩 vs 静态压缩

这是很多同学容易混淆的地方。我直接画个图来说明。

动态压缩 vs 静态压缩 对比图 动态压缩 用户请求 → 实时压缩 每次请求都压缩一次 CPU 开销高,响应延迟 静态压缩 构建时预生成 .gz 文件 用户请求直接返回压缩文件 零 CPU 开销,极快响应 选择建议 高并发、静态资源 → 静态压缩(构建时预生成) 动态内容、API 响应 → 动态压缩(级别 2-3)

动态压缩:每次用户请求时,服务器实时压缩文件。优点是灵活,适合动态生成的内容。缺点是每次请求都要消耗 CPU,高并发下压力很大。

静态压缩:在构建或部署时,提前生成 .gz 文件。用户请求时,服务器直接返回预压缩的文件。优点是零 CPU 开销,响应极快。缺点是需要额外的存储空间和构建步骤。

我在项目中遇到过一个问题:一个电商网站首页的 JS 文件有 500KB,动态压缩用了级别 6,结果高峰期服务器 CPU 飙到 90%。后来改成静态压缩,CPU 直接降到 20%。

我的建议:对于静态资源(JS、CSS、字体等),一定要用静态压缩。用 webpack 的 compression-webpack-plugin 或者直接命令行 gzip 预生成。对于 API 响应,用动态压缩,级别设 2-3 就够了。

避坑指南

最后分享几个我踩过的坑:

  • 不要压缩已经压缩过的资源:图片(jpg、png、webp)、视频(mp4)、压缩包(zip、rar)本身已经是压缩格式,再 Gzip 基本没效果,反而浪费 CPU。
  • 注意代理缓存:如果用了 CDN 或反向代理,一定要配置 Vary: Accept-Encoding 头。否则缓存服务器可能把压缩后的内容返回给不支持 Gzip 的客户端。
  • 小文件不要压缩:小于 1KB 的文件,压缩后可能反而变大(因为要加头部信息)。设置 gzip_min_length 1024 可以避免这个问题。
  • 我曾经犯过一个错:在 Nginx 里同时开启了动态压缩和静态压缩,结果静态文件被压缩了两次,客户端解压后反而乱码。解决方案是配置 gzip_static on,让 Nginx 优先使用预压缩文件。

Gzip 这东西,配置对了能省 60%-70% 的带宽,配置错了就是 CPU 杀手。希望今天的内容能帮你少走弯路。