Nginx 如何实现缓存?缓存策略怎么配才能防击穿?
Nginx 如何实现缓存?如何配置缓存策略?
Nginx 的缓存能力是后端服务性能优化的关键手段。面试中常从"Nginx 有哪几种缓存""proxy_cache 和 fastcgi_cache 怎么选""如何防止缓存击穿"这几个角度考察,理解原理比背配置更重要。
Nginx 缓存的三大层次
Nginx 缓存并不是单一机制,而是分布在请求链路的不同位置:
- 浏览器缓存:通过响应头(
Cache-Control、Expires)让客户端自行缓存,Nginx 只负责下发头信息 - 代理缓存(proxy_cache):Nginx 作为反向代理时,缓存后端上游的响应,适用于反向代理场景
- FastCGI 缓存(fastcgi_cache):缓存 FastCGI 协议上游(如 PHP-FPM)的响应,适用于 PHP 直连场景
面试时先说清楚这三层,再深入其中一层的配置细节,逻辑比直接贴配置更清晰。
代理缓存配置详解
nginxhttp { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off; server { listen 80; server_name example.com; location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $http_cache_control; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } }
核心参数解读
| 参数 | 作用 | 注意点 |
|---|---|---|
levels=1:2 | 缓存目录分两级,避免单目录文件过多 | 值越大层级越深,1:2 是常用配置 |
keys_zone=proxy_cache:10m | 共享内存区域,存储缓存键和元数据 | 10m 约可存 8 万条键,按需调大 |
max_size=1g | 缓存磁盘上限 | 超出后 Nginx 自动淘汰最久未访问的缓存 |
inactive=60m | 60 分钟无访问则淘汰 | 与 proxy_cache_valid 无关,是另一条淘汰链 |
use_temp_path=off | 临时文件写入缓存目录而非系统临时目录 | 减少跨磁盘拷贝,生产环境建议开启 |
proxy_cache_valid 200 302 10m | 200/302 响应缓存 10 分钟 | 必须显式配置,否则不缓存 |
proxy_cache_key | 缓存键的计算方式 | 默认含 scheme + method + host + uri,带参请求需考虑是否加入 $args |
FastCGI 缓存配置
FastCGI 缓存适用于 Nginx 直连 PHP-FPM 的场景,参数与 proxy_cache 对称:
nginxhttp { fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:10m max_size=1g inactive=60m; server { location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_methods GET HEAD; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; include fastcgi_params; } } }
proxy_cache 与 fastcgi_cache 怎么选?
| 对比维度 | proxy_cache | fastcgi_cache |
|---|---|---|
| 适用场景 | 反向代理后端(任何 HTTP 服务) | 直连 PHP-FPM 等 FastCGI 进程 |
| 协议 | HTTP | FastCGI |
| 灵活性 | 更通用,后端不限语言 | 仅限 FastCGI 协议 |
| 生产推荐 | 微服务、多语言后端 | 纯 PHP 架构 |
两者不能同时作用于同一 location。如果用了 proxy_pass 就用 proxy_cache,用了 fastcgi_pass 就用 fastcgi_cache。
缓存风暴与缓存锁定
这是面试高频追问点。当缓存过期瞬间,大量并发请求同时穿透到后端,这就是缓存风暴(Cache Stampede)。
nginx# 缓存锁定:只放一个请求去后端取数据,其余等待 proxy_cache_lock on; proxy_cache_lock_timeout 5s; # 过期缓存兜底:后端异常时返回旧缓存 proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # 后台异步更新:命中过期缓存时异步刷新,不阻塞请求 proxy_cache_background_update on;
三者配合的执行逻辑:缓存过期 → proxy_cache_lock 放行一个请求 → 其余请求读 proxy_cache_use_stale 返回旧数据 → 新数据写入后所有请求命中。这就是完整的防击穿方案。
动态缓存控制
不是所有请求都该缓存。用 map 指令按条件跳过缓存:
nginx# 按 URI 跳过缓存 map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1; } # 按后端响应头跳过缓存 map $upstream_http_cache_control $skip_cache_by_header { ~*no-cache 1; ~*private 1; default 0; } # 组合条件 map $skip_cache$skip_cache_by_header $combined_skip { default 0; ~1 1; }
然后在 location 中使用:
nginxproxy_cache_bypass $combined_skip; proxy_no_cache $combined_skip;
proxy_cache_bypass 和 proxy_no_cache 的区别:bypass 是跳过缓存直接请求后端但可能将响应写入缓存;no_cache 则完全不写缓存。生产环境通常两者配合使用,确保该跳过的请求既不读缓存也不写缓存。
静态文件与浏览器缓存
静态资源的缓存走另一套逻辑,不经过 proxy_cache,直接由 Nginx 返回文件并设置浏览器缓存头:
nginxlocation ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; }
immutable 告诉浏览器:资源不会变,不需要发条件请求验证。搭配文件名加 hash(如 app.3a7b2c.js)效果最佳,更新时换文件名即可让浏览器重新请求。
缓存清除方案
Nginx 开源版不支持主动清除缓存,三种替代方案:
- 自然过期:通过
proxy_cache_valid设定 TTL,到期自动淘汰 - 第三方模块 ngx_cache_purge:支持按 URL 主动清除,需编译安装
- 删除缓存文件:根据
proxy_cache_key的 MD5 值定位文件路径并删除,rm -rf /var/cache/nginx/proxy可全量清除
生产环境中,最稳妥的方式是修改 proxy_cache_key 加入版本号参数,发布时更新版本号让旧缓存自然失效。
缓存命中率监控
通过 X-Cache-Status 响应头可观察缓存命中情况:
nginxadd_header X-Cache-Status $upstream_cache_status;
状态值含义:
| 状态 | 含义 |
|---|---|
| MISS | 未命中,请求穿透到后端 |
| BYPASS | 命中跳过条件,不走缓存 |
| EXPIRED | 缓存已过期,需重新获取 |
| STALE | 后端异常,返回过期缓存 |
| UPDATING | 缓存正在更新,返回旧内容 |
| HIT | 命中缓存,直接返回 |
监控思路:统计 HIT / (HIT + MISS + EXPIRED) 的比值,低于 80% 就需要调优缓存键或 TTL。
缓存配置的常见踩坑
1. 后端响应头导致缓存不生效
后端返回 Cache-Control: no-cache 或 Set-Cookie 时,Nginx 默认不缓存。需要忽略这些头:
nginxproxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;
2. 缓存键缺少关键参数
默认 proxy_cache_key 不含 $args,但 API 请求 ?page=1 和 ?page=2 应该返回不同内容,需要加入查询参数:
nginxproxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
3. 缓存最小请求次数导致首次不缓存
proxy_cache_min_uses 2 表示请求出现 2 次才缓存,低流量接口可能永远不缓存。生产环境建议设为 1。
4. keys_zone 过小导致缓存元数据丢失
keys_zone 只存键和元数据,不存响应体。10m 约存 8 万条键,key 较长时需适当增大。
生产环境完整配置参考
nginxhttp { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:100m max_size=10g inactive=60m use_temp_path=off; fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:100m max_size=10g inactive=60m; map $request_uri $skip_cache { default 0; ~*/admin/ 1; ~*/api/ 1; ~*/user/ 1; } server { listen 80; server_name example.com; location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } location / { proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri"; proxy_cache_bypass $skip_cache; proxy_no_cache $skip_cache; proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_background_update on; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; } location ~ \.php$ { fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; } } }