一、缓存的本质:用磁盘/内存换响应时间
动态网站的每次请求都要经过"Nginx → PHP-FPM → MySQL"全链路处理,高并发下数据库首当其冲。缓存的思路很简单:把已经生成的页面或接口响应缓存起来,后续相同请求直接由 Nginx 命中返回,不再穿透到后端。一个合理的缓存命中率能减少 80% 以上的后端压力,响应时间从几百毫秒降到个位数毫秒。Nginx 提供两条缓存路线:proxy_cache(缓存上游代理响应,适合 API、反代场景)和 fastcgi_cache(缓存 FastCGI 如 PHP 的输出,适合动态建站)。
二、proxy_cache:反向代理层缓存
当你用 Nginx 反代到后端服务(如 Docker 内的应用、Java 接口、上游 CDN 源站)时,可在 Nginx 层直接缓存响应:
http {
# 定义缓存目录与共享内存,keys_zone 控制元数据内存
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:100m
inactive=60m max_size=2g;
server {
location / {
proxy_pass http://backend;
# 命中缓存直接返回,Miss 时向上游取数
proxy_cache proxy_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 10m; # 200 状态缓存 10 分钟
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating; # 上游异常时回退过期缓存
add_header X-Cache-Status $upstream_cache_status; # 便于观察命中情况
}
}
}
访问后看响应头 X-Cache-Status:HIT 表示命中缓存,MISS 表示未命中回源,EXPIRED/UPDATING 表示过期刷新中。这是排查缓存是否生效最直观的手段。inactive 是缓存条目多久不被访问即删除,max_size 限制磁盘占用,超出后 Nginx 自动淘汰最久未用的。
三、fastcgi_cache:给 PHP 页面加缓存
LNMP 环境里 PHP 页面走 FastCGI,用 fastcgi_cache 把生成的 HTML 直接缓存,比 proxy_cache 更贴近"页面级缓存":
http {
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2
keys_zone=php_cache:100m inactive=30m max_size=2g;
server {
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 开启页面缓存
fastcgi_cache php_cache;
fastcgi_cache_key "$request_method|$host|$request_uri";
fastcgi_cache_valid 200 5m; # 正常页面缓存 5 分钟
# 绕过规则:带登录 Cookie 或 POST 请求不缓存
fastcgi_cache_bypass $cookie_userid $arg_nocache;
fastcgi_no_cache $cookie_userid $arg_nocache;
add_header X-Fastcgi-Cache $upstream_cache_status;
}
}
}
两个关键的"旁路"指令务必理解:fastcgi_cache_bypass 让符合条件的请求(如已登录用户、带 nocache 参数)直接穿透到 PHP,fastcgi_no_cache 让这类响应不被写入缓存。否则登录用户的个性化内容会被缓存错发给其他用户,这是新手最容易犯的错误。
四、缓存 key 与分级缓存策略
缓存 key 决定"什么样的请求算同一个缓存"。fastcgi_cache_key 太宽(漏掉关键维度)会导致缓存串数据,太窄(带上每个参数)则命中率骤降。常见取舍:
# 页面缓存 key:方法 + 域名 + URI(忽略动态参数可提升命中,但要评估参数是否影响内容) fastcgi_cache_key "$request_method|$host|$request_uri";
实践中的分级策略:
- 静态资源(图片/CSS/JS):交给浏览器缓存 + Nginx
expires 7d,几乎零成本。 - 公开动态页(文章列表、首页):fastcgi_cache 缓存 1~10 分钟。
- 接口数据:proxy_cache 按接口 TTL 缓存,或交给后端自己的缓存层。
- 个性化内容:一律 bypass,靠数据库与 Redis 解决。
统一在 location / 上加 expires 指令给静态文件设置浏览器缓存:location ~* \.(jpg|png|css|js|woff2)$ { expires 7d; access_log off; }
五、缓存清理:主动 purge 与过期淘汰
缓存不可能永远有效,内容更新后需要主动清理。三种常用手段:
# 1. 配置 proxy_cache_purge(需 ngx_cache_purge 模块,第三方)
# location /purge/ {
# allow 127.0.0.1;
# deny all;
# proxy_cache_purge proxy_cache "$scheme$host$request_uri";
# }
# 2. 在代码里变更后,直接删除缓存目录里对应的 key 文件
# 文件按 levels=1:2 分两级目录存放,可通过工具按 URL 反推路径删除
# 3. 让应用在发布/更新时修改 URL 版本参数(cache buster),旧缓存自然过期
没有 purge 模块时最稳妥的兜底是"版本号参数"与"较短的 TTL":把 TTL 压到可接受的更新延迟内(如 5 分钟),即使不主动清理,内容也会在数分钟内自动过期,不会造成长期错版。
六、缓存排查与避坑清单
# 命中情况统计 curl -I https://your-site.com/ | grep -i 'x-cache\|x-fastcgi' # 查看缓存磁盘占用 du -sh /var/cache/nginx/*
高频坑与对策:
- 缓存串号/缓存错数据:key 没包含区分维度(域名、语言、设备),务必细化 key。
- 登录态被缓存:给带 Cookie 的请求加 bypass/no_cache。
- HIT 率奇低:先看 key 是否带了过多动态参数,或 TTL 太短,或缓存目录权限不对(Nginx worker 需可写)。
- 改了页面不生效:确认没有强缓存 + 后端 Cache-Control 未被忽略,必要时主动 purge。
- HTTPS 与 HTTP 混合:key 含
$scheme,避免两种协议互相串缓存。
写在最后
缓存是性价比最高的性能优化手段之一,但"缓存与一致性"的平衡才是核心难点。务实的起步方案:静态资源交给浏览器与 expires,公开页面用 fastcgi_cache 配 5 分钟 TTL,接口按需 proxy_cache,登录与个性化请求一律绕过,再通过 X-Cache-Status 头观察命中率逐步调优。先跑起来、能监控、再收紧,比一开始就追求"极致命中率"更稳妥,也能让你的站点在流量突增时从容不迫。




