CDN 缓存命中率是衡量加速效果的核心指标。当命中率低于 80% 时,大量请求会穿透到源站,导致带宽成本飙升、页面加载变慢。许多运维人员习惯直接套用默认缓存规则,却忽略了业务特性与 Cache-Control 指令的精细配合。下面从实战角度拆解 7 个技巧,覆盖配置逻辑、缓存键设计、动态内容处理等关键环节。
1. 按文件类型分级设置缓存时间
不同类型的资源对新鲜度要求差异极大。静态图片、字体、CSS/JS 库文件可以长期缓存(如 30 天),而 HTML 页面、API 响应则需要更短的有效期。如果不加区分地统一设置 max-age,要么导致内容更新滞后,要么浪费缓存能力。
操作步骤
- 在源站 Nginx 或 Apache 中按文件扩展名添加 Cache-Control 头:
location ~*.(jpg|png|gif|svg|ico)$ { add_header Cache-Control "public, max-age=2592000, immutable"; } - 对于 CSS/JS 文件,使用版本号参数(如 app.css?v=2)并设置 1 年 max-age。注意:immutable 指令可避免浏览器在刷新时重新校验。
- HTML 页面保持较短的缓存(如 max-age=300),配合 ETag 实现条件请求。
为什么这么做? 分级策略让 CDN 节点可以放心缓存大体积静态资源,同时保证页面内容的实时性不被破坏。很多缓存命中率偏低,正是因为 HTML 与静态资源共用相同的 long max-age,导致用户看到过期页面。
2. 用 Cache-Control: stale-while-revalidate 消除缓存空洞
当缓存过期后,并发请求会同时回源,造成源站突增压力。使用 Cache-Control: public, max-age=600, stale-while-revalidate=86400 可以让 CDN 在过期后的一段时间内仍返回旧缓存,同时在后台异步重新验证。这样用户永远能快速拿到内容,不会因回源等待而超时。
适用场景
- 新闻 API、商品列表等有一定容忍度的动态接口。
- 频繁更新的图片或视频封面图。
注意: stale-while-revalidate 依赖于 CDN 和浏览器支持。主流的 Cloudflare、Akamai、Fastly 均已支持,但部分国内 CDN 需要额外配置。建议先在小流量测试,确认节点行为符合预期。
3. 合理利用 s-maxage 区分代理缓存与私有缓存
很多开发者混淆了 max-age 与 s-maxage。s-maxage 仅对共享缓存(CDN、代理服务器)生效,优先级高于 max-age。例如:Cache-Control: public, max-age=0, s-maxage=3600
浏览器不会缓存该页面(max-age=0),但 CDN 会缓存 1 小时。这非常适合个性化内容低、但需要用户每次都请求最新数据的场景(如用户首页的公共推荐部分)。
错误示范
- 直接在响应头写死
Cache-Control: private,导致 CDN 完全无法缓存。应该用Cache-Control: public, s-maxage=3600配合private表达式区分区域。
4. 精细化缓存键设计:忽略无关参数
CDN 默认将查询参数作为缓存键的一部分。如果 URL 携带了随机数(如 ?t=123456)或 UTM 追踪参数,会导致同一资源被缓存成多个副本,命中率骤降。
优化方法
- 在 CDN 控制台设置“缓存键忽略参数”策略,仅保留必要的参数(如版本号、分页)。
- 对于追踪参数,使用重写规则或 CDN 的“忽略全部参数”功能。
- 如果必须保留参数,考虑使用规范化算法(如按参数名排序后计算哈希)作为缓存键。
实际效果: 忽略追踪参数后,某电商网站的图片缓存命中率从 45% 跃升至 92%。
5. 主动预缓存热点资源,避免冷启动
新发布的版本或长时间未访问的资源在首次请求时会穿透到源站。手动预热可以提前填充 CDN 节点。许多 CDN 服务商提供预热 API,调用后可把指定 URL 分发到所有边缘节点。
执行策略
- 每次发布前,将变更的文件清单(如 main.js, style.css)通过预热接口提交。
- 针对季节性热点(如双十一活动页面),提前 24 小时开始渐进式预热。
- 监控节点覆盖情况,确保预热任务完成后验证缓存状态。
注意: 预热会消耗回源带宽,建议在低峰期执行。同时要预热源站能承受相应压力。
6. 利用 vary 头正确处理编码与语言
当源站根据请求头(User-Agent、Accept-Encoding、Accept-Language)返回不同内容时,必须使用 Vary 头告知 CDN。例如:Vary: Accept-Encoding, Accept-Language
如果不设置 Vary,CDN 可能会把 gzip 压缩版本发给不支持的解压客户端,或者把英文页面展示给中文用户。
陷阱与解决方案
- 不要过度使用 Vary。每增加一个 vary 维度,缓存副本数会成倍增长,降低命中率。如果只区分 gzip 和 non-gzip,通常 CDN 会自动处理(很多 CDN 内部会按是否压缩分开缓存),无需手动加 Vary。
- 如果必须按语言不同内容,可以考虑用子域名(en.example.com, zh.example.com)代替 vary,这样缓存可以独立、均匀分布。
7. 监控与调整:度量命中率,发现异常
没有数据的优化是盲目的。CDN 服务商通常提供命中率报表,但需要理解其计算口径:是请求数命中率还是字节命中率?请求数命中率对静态资源有利,字节命中率更能反映带宽节省。
关键指标
- 请求数命中率 = 缓存命中的请求数 / 总请求数。参考值:静态资源 > 95%,动态 API > 70%。
- 字节命中率 = 从缓存返回的字节数 / 总字节数。理想值 > 90%。
- 回源率 = 回源请求数 / 总请求数。应低于 5%。
异常排查
- 命中率骤降:检查是否有新版本发布导致 max-age 缩短、缓存键变动或者 CDN 节点清空。
- 回源率突然升高:查看是否源站设置了 Cache-Control: no-cache 或 s-maxage=0。
- 不同地区命中率差异大:可能是某些边缘节点预热不完整,或流量分布不均。
附录:一个典型的 Cache-Control 策略组合
假设一个新闻门户网站,包含:
- 首页 HTML:
public, max-age=300, s-maxage=600(浏览器缓存 5 分钟,CDN 缓存 10 分钟) - 文章列表 API:
public, max-age=60, s-maxage=300, stale-while-revalidate=3600(CDN 缓存 5 分钟,过期后 1 小时内可返回旧数据并异步更新) - 文章详情页:
public, max-age=0, s-maxage=86400(浏览器不缓存,CDN 缓存 1 天,适合快速更新) - 图片/JS/CSS:
public, max-age=31536000, immutable(浏览器和 CDN 缓存 1 年,配合版本号实现即时更新)
整套策略实施后,该网站缓存命中率从 68% 提升至 96%,源站带宽减少 80%,用户感知加载速度提升 3 倍。
以上 7 个技巧并非孤立使用,而是需要根据业务特点组合配置。建议先在一个 CDN 节点上做 A/B 测试,观察命中率与回源错误率的变化,再逐步推广到全量。缓存调优没有银弹,持续监控和微调才是保持高命中率的关键。
延伸阅读
