缓存与压缩是 Nginx 提升响应速度、降低上游压力的两大杠杆。缓存把热点响应"截留"在网关层,让后端从重复计算中解放出来;压缩把传输体积缩小数倍,显著降低带宽消耗与首字节延迟。但缓存配置一旦出错,会面临陈旧数据被反复返回、缓存击穿把后端打崩、压缩级别过高反而拖慢 CPU 等问题。本文系统讲解 proxy_cache 的目录与键设计、有效期策略、命中率观测,以及 gzip/Brotli 的压缩调优。
核心认知:缓存的价值取决于命中率与新鲜度,压缩的价值取决于体积与 CPU 的平衡。两者联动时,先压缩后缓存或缓存压缩后的内容,收益才能最大化。
1. 静态资源缓存
1.1 浏览器缓存头
location ~* \.(css|js|png|jpg|jpeg|gif|webp|svg|woff2)$ {
expires 30d; # 输出 Cache-Control: max-age=2592000
add_header Cache-Control "public, max-age=2592000, immutable";
}
| 资源类型 | 推荐策略 |
|---|---|
| HTML | no-cache,每次都校验 |
| CSS/JS | 带版本指纹,max-age 长缓存 |
| 图片/字体 | public, max-age=31536000, immutable |
| API 响应 | 按业务定,常用 no-store 或短缓存 |
1.2 版本化文件名
# 带 hash 指纹的静态资源可以放心长缓存
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
避坑:缓存了 HTML 里引用的旧资源会导致用户拿到过期的 JS/CSS。正确做法是文件名带内容 hash(如
app.a1b2c3.js),发布新版本即换 URL。
2. proxy_cache 基础
2.1 缓存目录与键
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:100m
inactive=60m max_size=2g;
| 参数 | 含义 | 推荐值 |
|---|---|---|
levels=1:2 | 目录分级,避免单目录文件过多 | 1:2 |
keys_zone | 内存中的键表(名称:大小) | 10m~100m |
inactive | 多久未访问则清理 | 60m |
max_size | 磁盘缓存上限 | 视磁盘而定 |
use_temp_path | 是否用临时路径写缓存 | off |
2.2 启用缓存
location / {
proxy_pass http://backend;
proxy_cache mycache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
}
| 指令 | 作用 |
|---|---|
proxy_cache | 指定使用哪个 keys_zone |
proxy_cache_key | 定义缓存键(决定复用粒度) |
proxy_cache_valid | 按状态码设定有效期 |
proxy_cache_bypass | 命中条件时跳过缓存直接回源 |
核心认知:缓存键决定"哪些请求共享一份缓存"。键越粗命中率越高但粒度越粗,键里带上 Cookie/参数会显著降低命中率,需要按业务权衡。
3. 有效期策略与缓存控制
3.1 按状态码与响应头控制
location /api/ {
proxy_pass http://backend;
proxy_cache mycache;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 500 502 503 1m;
proxy_cache_valid any 1m;
proxy_ignore_headers Cache-Control Set-Cookie; # 忽略上游缓存头
}
3.2 绕过与主动刷新
# 带 ?nocache=1 的请求绕过缓存直接回源
location /api/ {
proxy_pass http://backend;
proxy_cache mycache;
proxy_cache_bypass $arg_nocache;
proxy_no_cache $arg_nocache;
}
# 指定请求头也能触发绕过(如内部刷新接口)
proxy_cache_bypass $http_x_purge;
3.3 缓存未命中穿透保护
proxy_cache_lock on; # 同键并发请求只放一个回源,其余等待
proxy_cache_lock_timeout 5s; # 等待超时后也放行
proxy_cache_use_stale updating; # 回源期间用旧缓存兜底
避坑:未开启
proxy_cache_lock时,热点键失效瞬间会有大量请求同时回源(缓存击穿)。开启 lock + use_stale 能显著缓解雪崩风险。
4. 命中率观测
4.1 X-Cache-Status 响应头
add_header X-Cache-Status $upstream_cache_status;
| 值 | 含义 |
|---|---|
| HIT | 命中缓存 |
| MISS | 未命中,回源 |
| EXPIRED | 已过期,重新验证 |
| UPDATING | 正在回源更新,用旧值 |
| BYPASS | 被 proxy_cache_bypass 跳过 |
| STALE | 回源失败,用陈旧缓存 |
4.2 命中率统计
# 从 access log 统计缓存命中率
awk '{print $NF}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn
# 期望 HIT 占比在 80% 以上
4.3 定期分析
| 指标 | 目标值 | 优化手段 |
|---|---|---|
| HIT 占比 | > 80% | 加长有效期、收窄 key |
| MISS 占比 | 越低越好 | 预热、排查动态参数 |
| EXPIRED | 观察 | 调整 proxy_cache_valid |
| BYPASS | 排查 | 确认是否误设 cache_bypass |
核心认知:先观察
X-Cache-Status分布再调整缓存参数。盲目拉长有效期会提高命中率,但可能把旧数据留在边缘节点,新鲜度与命中率需要平衡。
5. gzip 压缩
5.1 基础配置
gzip on;
gzip_min_length 1024; # 小于 1KB 不压缩
gzip_comp_level 5; # 1~9,默认 1,建议 4~6
gzip_types text/plain text/css application/json
application/javascript application/xml
image/svg+xml; # 只压缩文本类
gzip_vary on; # 输出 Vary: Accept-Encoding
gzip_disable "msie6";
5.2 压缩级别权衡
| 级别 | 压缩率 | CPU 消耗 | 适用 |
|---|---|---|---|
| 1~3 | 较低 | 低 | 高 QPS、CPU 紧张 |
| 4~6 | 均衡 | 中 | 生产推荐 |
| 7~9 | 更高 | 高 | 低 QPS、重 IO |
避坑:图片、视频本身已是压缩格式,gzip 再压收益极小还浪费 CPU,务必只对文本类 MIME 开启。
gzip_min_length太小会压缩小响应,性价比低。
6. Brotli 与静态预压缩
6.1 Brotli 配置
Brotli 压缩率比 gzip 更高,通常需要第三方模块(如 ngx_brotli):
brotli on;
brotli_min_length 1024;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
| 对比 | gzip | Brotli |
|---|---|---|
| 压缩率 | 基准 | 高 10%~20% |
| 解压速度 | 快 | 略慢 |
| 浏览器支持 | 全 | 现代浏览器 |
| 模块 | 内置 | 需编译 ngx_brotli |
6.2 静态预压缩
对于发布时已确定不变的大文件,可以预先生成 .gz/.br 文件,让 Nginx 直接发送:
gzip_static on; # 命中同名 .gz 文件直接发送
brotli_static on; # 命中同名 .br 文件直接发送
# 构建期预压缩
gzip -k -9 dist/app.js
brotli -k -q 11 dist/app.js
核心认知:预压缩把压缩成本从"请求时"转移到"构建时",还能用更高的压缩级别,是静态站点性能优化的高性价比方案。
7. 缓存与压缩联动
7.1 组合配置模板
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:50m
inactive=60m max_size=1g;
server {
location / {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_lock on;
proxy_cache_use_stale updating error timeout;
add_header X-Cache-Status $upstream_cache_status;
gzip on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript;
gzip_vary on;
}
}
7.2 缓存与压缩的顺序
请求 → 网关层(命中缓存?)→ 命中则返回压缩缓存 / 未命中回源
→ 上游响应 → 按需 gzip 压缩 → 写入缓存 → 返回客户端
| 实践 | 说明 |
|---|---|
| 缓存压缩后内容 | 减少重复压缩 CPU |
| 缓存 Accept-Encoding 区分 | 避免返回错编码(key 里加编码变量) |
| 后端已压缩则前端跳过 | 用 $http_accept_encoding 判断 |
| 预热热点键 | 发布后主动请求一次,避免首击 MISS |
7.3 缓存清理
# 按 key 主动清理缓存(需 ngx_cache_purge 模块)
curl -X PURGE http://example.com/api/user/1
# 或直接删除磁盘缓存文件
find /var/cache/nginx -name "*" -type f -mtime +1 -delete
避坑:缓存键若未包含
Accept-Encoding,gzip 与不 gzip 的响应会互相污染缓存,导致客户端收到无法解压的响应。必须把编码变量纳入缓存键或严格区分存储。
8. 总结
| 环节 | 要点 |
|---|---|
| 静态缓存 | 版本化文件名 + immutable 长缓存 |
| 缓存目录 | proxy_cache_path 分级目录 + keys_zone |
| 缓存键 | scheme/method/host/uri 组合,勿滥用 Cookie |
| 有效期 | proxy_cache_valid 按状态码差异化 |
| 防击穿 | proxy_cache_lock + use_stale |
| 命中率 | 用 X-Cache-Status 观测并调参 |
| 压缩 | gzip 4~6 级,只压文本;Brotli 更高 |
| 联动 | 缓存压缩内容,预热热点键 |
缓存把压力留在边缘,压缩把流量减在路上。两者配合能让后端 QPS 压力下降一个数量级。但缓存与压缩都依赖精准的观测,下一篇将用日志体系把命中率、耗时与状态码全部可视化。
延伸阅读
- Nginx 核心配置结构 — location 与指令上下文基础
- Nginx 反向代理与负载均衡 — 上游响应进入缓存的路径
- Nginx HTTPS 与 TLS 加固 — 加密流量下的缓存安全
- Nginx 限流与安全防护 — 缓存节点访问控制
- Nginx 日志分析与性能调优 — 命中率与耗时分析
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。