CDN 缓存键:一个决定命中率的隐藏开关

缓存键(cache key)是 CDN 判断“这个请求能不能命中缓存”的依据。很多人只设置缓存时长,却不管缓存键,结果要么命中率上不去,要么不同用户看到同一个页面却是错乱的内容。缓存键之所以重要,是因为它直接决定了同一份内容会被缓存成多少份、又能被多少个请求复用。这篇讲清楚缓存键由什么组成,以及怎么配置才能既提高命中率又不把内容搞混。

缓存键由什么组成

先看一个直观的例子:https://example.com/product/42?lang=zh&utm_source=weibohttps://example.com/product/42?lang=zh&utm_source=baidu,页面内容其实一模一样,只是来源参数不同。如果缓存键连 query 都算进去,这两次请求就会各自缓存一份,浪费空间还拉低命中率;如果只按“路径 + lang”算,它们就能共用同一个缓存副本。缓存键通常是几个维度的组合:

维度 是否该纳入 说明
URL 路径 ✅ 必须 不同页面必然不同
Host ✅ 必须 多域名时避免串站
Query 参数 ⚠️ 白名单 只保留影响内容的参数
Cookie / 请求头 ⚠️ 慎用 只对认证类接口使用
语言 ✅ 按需 多语言站点必须区分

设计原则

  • 静态资源按文件哈希做强缓存app.css 发布后改名成 app.a1b2c3.css,内容变了文件名就变,缓存键天然失效,可以放心设一年有效期;
  • HTML 页面按路径 + 语言区分/zh-cn/about/en-us/about 是不同内容,缓存键里必须带上语言前缀,否则英文用户会看到中文页面;
  • 对营销参数做白名单?utm_source=xxx&utm_campaign=yyy 这类参数不影响页面内容,放进来只会把同一页拆成无数个缓存副本,命中率直线下降;
  • 登录态与购物车接口禁止缓存:这些接口因人而异,缓存了轻则串数据,重则泄露隐私。

怎么监控缓存命中率

缓存键配得对不对,最终都要看命中率数据。主流 CDN 后台都有命中率报表:Cloudflare 在 Caching 面板看 Cache Hit Ratio,阿里云 CDN 在监控里看命中率趋势。判断标准很简单——静态资源命中率应该在 90% 以上,动态页面可以低一些,但别长期低于 40%。如果命中率突然下降,先看是不是改过缓存键、加了新参数,或者源站响应头里的 Cache-Control 被意外改动;排查时按改动时间对一下发布记录,通常很快就能找到元凶。

缓存失效:改版后怎么让旧缓存尽快下线

缓存键决定命中,缓存失效决定“更新多快生效”。改版后最怕旧页面还留在边缘节点:要么等 TTL 自然过期,要么主动清理。两个实用手段:一是版本化资源,改 JS/CSS 就换文件名,新页面自然引用新文件;二是发布后用 CDN 的 purge 功能批量清理相关 URL。别在高峰时段全量刷新,否则会瞬间把回源流量打满,反而制造新的故障。养成“发布即清理、清理有范围”的习惯,比依赖 TTL 过期可靠得多。

配置示例:Cloudflare 与 Nginx

Cloudflare 里可以用 Cache Rules 控制缓存键:

{
  "expression": "(http.host eq \"example.com\")",
  "action": {
    "type": "set_cache_settings",
    "cache_key": {
      "include_query": false,
      "include": ["header:accept-language"]
    }
  }
}

Nginx 里用 proxy_cache_key 自定义缓存键:

location / {
    proxy_cache_key "$host$request_uri";
    # 静态资源按文件哈希做缓存键
    location ~* \.(css|js|png|jpg)$ {
        proxy_cache_key "$host$uri";
        expires 1y;
    }
}

两边的思路一致:动态页面把 $request_uri(含 query)排除出缓存键,静态资源直接按 URI 缓存一年。

一个真实场景:多语言电商站

假设你运营一个中英文电商站,商品页带 ?lang=zh&utm_source=weibo 这类参数。如果缓存键包含全部 query,那么 ?lang=zh&utm_source=weibo?lang=zh&utm_source=baidu?lang=en 都会各自缓存一份,同一商品可能被拆成几十个缓存副本。正确做法是:把 lang 纳入缓存键,把 utm_* 排除。改完之后命中率可能从 40% 涨到 90% 以上,回源压力大幅下降,页面打开速度也随之变快。下面是一组典型的改前改后对比:

配置 缓存副本数 命中率 回源压力
全部 query 入键 几十份/商品 约 40%
只保留 lang 2-3 份/商品 90%+ 大幅下降

常见问题

  • Query 参数全部参与缓存键导致命中率下降:解决方法是白名单,只保留真正影响内容的参数(比如 langpage),其余一律剔除;
  • 多语言页面未区分语言导致内容错乱:在缓存键里显式加入语言维度,或在 URL 路径里带上语言前缀;
  • 登录后看到别人的数据:认证类页面不要走公共缓存,用 Cache-Control: private 或直接绕过 CDN;
  • 改了源站内容页面却没变:边缘节点还在 TTL 内的旧缓存里。先确认是不是缓存键没变导致命中旧副本,再决定等过期还是主动 purge。

参考:Cloudflare Cache Keys 文档 https://developers.cloudflare.com/cache/how-to/cache-keys/ ,Nginx proxy_cache_key 文档 https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_key