Nginx 缓存策略与动静分离实践
CDN 前移与边缘缓存固然重要,源站 Nginx 的缓存与动静分离同样关键。本文给出缓存键、缓存层级与缓存失效策略。
1. CDN 的概念与原理
CDN(Content Delivery Network,内容分发网络)通过在全球各地部署边缘节点,将内容缓存并就近分发给用户,以缩短 RTT、降低回源压力和抖动。其核心机制包括:
- 路由调度:基于 Anycast、DNS 或 HTTP 重定向,将用户引导到就近/最优节点。
- 边缘缓存:边缘节点依据源站响应头(如
Cache-Control、Expires、ETag、Last-Modified、Vary、Surrogate-Control、s-maxage等)决定存储与回源策略。 - 回源与验证:命中失败或过期后,边缘向源站回源;支持条件请求(
If-None-Match/If-Modified-Since)。 - 失效与刷新:通过 API 触发 URL/PATH/Tag 维度的失效(Soft/Hard Purge),或自然到期。
实践上,合理设计缓存层级(浏览器 → CDN 边缘/中间层 → 源站 Nginx)与一致的缓存语义,是稳定与性能的关键。
2. 从 Nginx 反向代理角度:职责与作用
Nginx 作为源站或中间层反向代理,承担:
- 协议终止:TLS 终止、HTTP/2/HTTP/3(QUIC),HSTS、安全头治理。
- 流量治理:限流、熔断、重试、健康检查、连接复用(keepalive)、负载均衡(轮询/一致性哈希)。
- 路由与动静分离:路径/主机名路由,将静态交给文件系统/对象存储,动态交给应用上游。
- 缓存与加速:
proxy_cache/fastcgi_cache/uwsgi_cache微缓存,削峰填谷(use_stale/background_update)。 - 可观测性:接入/上游日志、
$upstream_cache_status、自定义头(如X-Cache-Status)。
3. Nginx 缓存原理与类型
Nginx 缓存本质是以“缓存键 → 缓存对象(响应头+体)”的 KV 存储,命中由“键一致 + 仍在有效期 + 可用状态”决定。
- 缓存类型:
proxy_cache:反向代理上游(HTTP)fastcgi_cache:PHP/FPM 等 FastCGI 应用uwsgi_cache:uWSGI 协议上游
- 核心要素:
- 缓存键:常见构成为
scheme + method + host + uri + args + 关键头/变量,需谨慎纳入Cookie/User-Agent/Accept-Language/Device等差异维度,避免过度碎片化。 - 有效期:
proxy_cache_valid(命中后 TTL),与上游头(Cache-Control/Expires)的关系可通过proxy_ignore_headers、proxy_cache_revalidate on;调整。 - 失效策略:自然过期、主动清理(
ngx_cache_purge或重新变更缓存键/版本位)。 - 抖动优化:
proxy_cache_lock(合并并发 miss)、use_stale updating/error(错误/更新期间复用旧值)、proxy_cache_background_update(后台刷新)。
- 缓存键:常见构成为
4. 缓存配置示例(微缓存 + 静态长缓存)
http {
upstream api_upstream {
server 127.0.0.1:8080;
keepalive 64;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:256m inactive=1d max_size=20g use_temp_path=off;
# 登录/写操作等绕过缓存
map $http_cookie $bypass_cache_cookie { default 0; ~*(session|token|auth) 1; }
map $request_method $bypass_cache_method { default 0; ~^(POST|PUT|PATCH|DELETE)$ 1; }
map "$bypass_cache_cookie$bypass_cache_method" $bypass_cache { default 0; "10" 1; "01" 1; "11" 1; }
# 可选:版本位,灰度/全量刷新时提升为新版本(与 CDN Tag/Key 搭配)
# map $http_x_cache_version $cache_version { default "v1"; }
server {
listen 443 ssl http2;
server_name www.example.com;
# 与 CDN/前置代理配合的真实 IP 处理(将网段替换为实际 CDN 出口段)
# set_real_ip_from 203.0.113.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on;
# API 微缓存:强削峰、弱一致(1s~3s 级别)
location /api/ {
proxy_pass http://api_upstream;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api_cache;
proxy_cache_key "$scheme$request_method$host$request_uri"; # 可拼入 $cache_version 等变量
proxy_cache_lock on; # 合并并发 miss,避免狗群效应
proxy_cache_lock_timeout 5s;
proxy_cache_background_update on; # 后台刷新
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_valid 200 1s; # 微缓存 TTL(示例)
proxy_cache_valid 301 302 10m;
proxy_cache_valid any 1m; # 其他状态的保守缓存
proxy_no_cache $bypass_cache; # 条件下不写入缓存
proxy_cache_bypass $bypass_cache; # 条件下不读取缓存
add_header X-Cache-Status $upstream_cache_status always;
}
# HTML:短 TTL + s-maxage 交给 CDN,浏览器不持久缓存
location = /index.html {
etag on; if_modified_since exact;
expires -1;
add_header Cache-Control "public, s-maxage=60, stale-while-revalidate=120, stale-if-error=86400" always;
add_header Vary "Accept-Encoding, Accept-Language" always;
try_files $uri /index.html;
}
}
# 静态域(动静分离):长缓存 + 指纹命名 + immutable
server {
listen 443 ssl http2;
server_name static.example.com;
root /var/www/static;
location / { try_files $uri =404; }
location ~* \.(?:css|js|woff2?|ttf|otf|eot|svg|png|jpg|jpeg|gif|ico)$ {
access_log off;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
}
}
5. 缓存删除与灰度刷新
- 源站侧:
ngx_cache_purge:对指定 URL/key 做强制清理(需相应模块/规则)。- 版本位切换:将
$cache_version/路径前缀/资源指纹切换到新版本,实现“旧缓存自然淘汰,新缓存即时生效”。 use_stale updating+background_update:对热点内容采用平滑刷新。
- CDN 侧:
- Tag/Key/路径维度的 API 失效(尽量 Soft Purge +
stale-while-revalidate,避免缓存雪崩)。 - 对 HTML/聚合页先 Soft 后 Hard,控制潮汐效应。
- Tag/Key/路径维度的 API 失效(尽量 Soft Purge +
6. CDN 与 Nginx 的协同(通信与对齐)
- 缓存语义:
- 浏览器用
max-age/immutable,CDN 用s-maxage/Surrogate-Control,两者各司其职。 - 条件请求:启用
ETag/Last-Modified,允许 CDN 及 Nginx 复用 304 验证,减少字节回源。 Vary对齐:与实际差异维度一致,避免误合并或碎片化;慎用Vary: Cookie(更推荐在 CDN 端剥离无关 Cookie)。
- 浏览器用
- 连接/身份:
- 透传链路:
X-Forwarded-For、X-Forwarded-Proto,并在源站恢复真实 IP(set_real_ip_from)。 - 观测头:统一
X-Cache(CDN)、X-Origin-Cache(Nginx)等便于排障。
- 透传链路:
- 回源与削峰:
- CDN 打开 Collapsed Forwarding/Origin Shield;Nginx 打开
proxy_cache_lock/use_stale,双层合并并发 miss。
- CDN 打开 Collapsed Forwarding/Origin Shield;Nginx 打开
7. 动静分离最佳实践
- 域名分离:
www.example.com(动态/HTML 短缓存)与static.example.com(静态资源长缓存)分域,便于策略与权限隔离。 - 指纹命名:静态资产使用内容哈希(如
app.<hash>.js),配合Cache-Control: immutable与 1 年 TTL。 - HTML/接口:
- HTML 由 CDN 短 TTL +
stale-while-revalidate,源站可微缓存 1s~3s 抗尖峰。 - API GET 可微缓存;写操作/登录态通过
Cookie/Method映射绕过缓存。
- HTML 由 CDN 短 TTL +
- 资源下沉:将图片/视频等大文件迁移至对象存储 + CDN 边缘,Nginx 只做签名鉴权与 302 跳转(或代理带范围请求)。
- 压缩与协议:开启
brotli/gzip(二选一优先 brotli),启用 HTTP/2/3,合理的 TLS 会话复用与 OCSP Stapling。
8. 注意事项(坑点清单)
- 语义不一致:源站与 CDN 的
Cache-Control冲突,导致双层缓存不可控;确保s-maxage与max-age区分清晰。 - 过度
Vary:将Cookie、User-Agent直接纳入键导致碎片化;优先在 CDN 端规整头部/剥离无关 Cookie。 - 个性化内容被缓存:涉及用户态/地域/AB 实验的页面需加
private, no-store或显式绕过。 - 重复压缩:CDN 与源站同开 gzip/brotli 可能叠加问题;只保留一处压缩(通常边缘)。
- 清缓存风暴:大规模 Hard Purge 触发回源雪崩;优先 Soft +
stale-while-revalidate,并打开合并回源。 - Range 与视频:确认 CDN 与源站均支持 Range;Nginx 需
aio on; directio等以优化大文件。 - 真 IP 获取:补全 CDN 出口网段至
set_real_ip_from,否则访问日志皆为边缘 IP。
9. 排错清单(最小化工具)
- 响应头核对:
curl -I https://www.example.com/ | sed -n '1,200p',检查Cache-Control、Age、ETag、Vary、X-Cache、X-Cache-Status。 - 分层命中:分别查看 CDN 命中(
X-Cache: HIT)与 Nginx 命中(X-Cache-Status: HIT)。 - 条件请求:
If-None-Match/If-Modified-Since是否 304;如无 ETag,考虑在 HTML/静态启用etag on;。 - 键确认:临时把
proxy_cache_key响应回显到头部以核对(调试时用,勿在生产保留)。
10. 小结
合理的“浏览器 → CDN → 源站 Nginx”三层缓存设计,配合动静分离与微缓存,可同时获得低时延、高吞吐与稳定性。关键在于:一致的缓存语义、谨慎的缓存键设计、可控的失效策略,以及双层削峰机制的配合。