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 静态压缩
这是很多同学容易混淆的地方。我直接画个图来说明。
动态压缩:每次用户请求时,服务器实时压缩文件。优点是灵活,适合动态生成的内容。缺点是每次请求都要消耗 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 杀手。希望今天的内容能帮你少走弯路。