Nginx 缓存策略与动静分离实践

CDN 前移与边缘缓存固然重要,源站 Nginx 的缓存与动静分离同样关键。本文给出缓存键、缓存层级与缓存失效策略。

1. CDN 的概念与原理

CDN(Content Delivery Network,内容分发网络)通过在全球各地部署边缘节点,将内容缓存并就近分发给用户,以缩短 RTT、降低回源压力和抖动。其核心机制包括:

  • 路由调度:基于 Anycast、DNS 或 HTTP 重定向,将用户引导到就近/最优节点。
  • 边缘缓存:边缘节点依据源站响应头(如 Cache-ControlExpiresETagLast-ModifiedVarySurrogate-Controls-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_headersproxy_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,控制潮汐效应。

6. CDN 与 Nginx 的协同(通信与对齐)

  • 缓存语义:
    • 浏览器用 max-age/immutable,CDN 用 s-maxage/Surrogate-Control,两者各司其职。
    • 条件请求:启用 ETag/Last-Modified,允许 CDN 及 Nginx 复用 304 验证,减少字节回源。
    • Vary 对齐:与实际差异维度一致,避免误合并或碎片化;慎用 Vary: Cookie(更推荐在 CDN 端剥离无关 Cookie)。
  • 连接/身份:
    • 透传链路:X-Forwarded-ForX-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。

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 映射绕过缓存。
  • 资源下沉:将图片/视频等大文件迁移至对象存储 + CDN 边缘,Nginx 只做签名鉴权与 302 跳转(或代理带范围请求)。
  • 压缩与协议:开启 brotli/gzip(二选一优先 brotli),启用 HTTP/2/3,合理的 TLS 会话复用与 OCSP Stapling。

8. 注意事项(坑点清单)

  • 语义不一致:源站与 CDN 的 Cache-Control 冲突,导致双层缓存不可控;确保 s-maxagemax-age 区分清晰。
  • 过度 Vary:将 CookieUser-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-ControlAgeETagVaryX-CacheX-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”三层缓存设计,配合动静分离与微缓存,可同时获得低时延、高吞吐与稳定性。关键在于:一致的缓存语义、谨慎的缓存键设计、可控的失效策略,以及双层削峰机制的配合。