为什么你的CDN缓存命中率始终上不去?
打开CDN监控面板,发现缓存命中率只有30%甚至更低——这是很多运维同学常见的烦恼。命中率低意味着大量请求穿透到源站,不仅拖慢用户访问速度,还会让带宽成本直线上升。但在抱怨CDN服务商之前,不妨先审视一下自己的配置。根据我的观察,超过80%的低命中率案例都可以通过策略调整解决,而不是CDN本身的问题。
缓存命中率的核心公式其实很简单:命中次数 / (命中次数 + 未命中次数)。但影响这个比值的变量非常多,包括缓存过期时间、URL唯一性、动态内容比例、预热覆盖率等。下面这五个技巧是经过实际验证、性价比最高的调优手段,每个都配合了具体操作建议。
技巧一:为不同资源类型设置差异化的缓存过期时间
不要再对所有文件用同一个TTL
很多团队在配置CDN时,习惯性地对所有静态资源设置统一的缓存时间,例如“7天”。这表面上省事,但实际效果很差:对于频繁更新的JS/CSS文件,7天太长导致用户得不到最新版本;对于几乎不变的图片、字体文件,7天又太短,白白浪费了缓存机会。
正确的做法是按文件扩展名或路径进行分级缓存。CDN通常会支持在控制台或通过响应头设置规则。我推荐以下分级策略:
- 版本化的JS/CSS(带hash):缓存一年(31536000秒)。因为文件名变化代表内容变化,旧资源可以永久缓存。
- 不变图片(PNG、JPG、WebP):缓存30天以上。如果源站图片很少替换,甚至可以设置到180天。
- 字体文件(WOFF、WOFF2):缓存一年。字体几乎不更新。
- HTML页面:短缓存(5~10分钟)或不缓存。HTML通常需要反映最新状态。
- API接口:根据数据变化频率设置。如果数据每小时变化一次,TTL设为3600秒;如果每次请求都不同,则完全不缓存。
实际操作中,你可以先在CDN控制台添加“文件类型缓存规则”,并同时在源站返回的Cache-Control响应头中做兜底。当两者冲突时,以时间较短的为准,所以确保源站头的设置合理也很重要。
技巧二:严格过滤URL中的无关参数,避免缓存碎片化
一个跟踪参数毁掉一个缓存
这是导致命中率低的最隐蔽原因。当用户访问https://example.com/app.js时,CDN会缓存这个资源。但如果某个页面在引用时加上了?v=123或?utm_source=google,CDN会把app.js?v=123和app.js?v=124当作两个完全不同的URL来缓存。即使源站返回的内容一模一样,缓存也会被切割成无数碎片,命中率自然上不去。
解决方案分两步走:
- 在源站层面:确保所有静态资源引用不使用动态参数。如果非要用版本号,请放在文件名里,比如
app.123.js。 - 在CDN层面:开启“忽略URL参数”功能(大部分CDN厂商支持)。开启后,CDN在查找缓存时会忽略
?后面的所有参数。注意:只有在你确定源站不会根据参数返回不同内容时才可开启,否则会导致错误的缓存结果。
如果确实需要根据参数区分内容(例如国际化参数?lang=en),那么可以设置“保留参数白名单”,只允许特定参数影响缓存,其他参数全部忽略。
技巧三:将动态内容与静态内容分离域名,并为动态请求设置缓存规则
别让动态请求污染静态缓存
很多网站把动态接口和静态资源放在同一个域名下,例如https://www.example.com/既有HTML、JS,又有/api/users这种接口。这会造成CDN的“双输”:
• 动态接口因为无法缓存,请求直接回源,浪费了CDN节点资源;
• 动态接口的URL可能与静态资源有相似路径,导致缓存规则难以精准覆盖静态资源。
最佳实践是使用独立的域名或子域名承载静态资源。例如:
- 静态资源:
static.example.com - 动态API:
api.example.com - 主站:
www.example.com
这样一来,针对static.example.com你可以非常激进地开启所有静态缓存优化(长TTL、忽略参数、开启压缩等),而不用担心影响动态请求。对于api.example.com,你也可以根据接口特性设置静态化缓存(例如将用户公共数据缓存30秒),进一步提升动态内容的整体加速效果。
技巧四:在业务低峰期主动预热缓存
等用户来触发缓存,永远是被动的
很多CDN采用“首次访问触发缓存”的模式。这意味着第一个用户请求某个资源时,CDN节点尚未缓存,必须回源拉取。这个回源过程不仅慢,而且算作一次未命中。如果该资源后续被大量访问,那么第一个用户就承受了最差的体验。
缓存预热的目的是在用户访问之前,提前将内容推送到CDN节点。具体做法:
- 使用CDN提供的预热API或工具:大多数厂商都支持通过控制台或API提交URL列表进行预热。你可以在部署代码后、流量高峰前手动或自动触发预热。
- 编写自动化脚本:结合CI/CD流水线,在每次发布新版本后,自动预热本次变更涉及的静态资源。例如,用Python脚本读取部署后的资源列表,调用CDN预热接口。
- 优先预热关键路径资源:首页、核心JS、CSS、大图等资源最好提前预热。对于低频访问的资源,预热意义不大,可以让CDN按需缓存。
预热时有一个常见误区:一次性预热太多URL却忽略了缓存时间。如果预热后TTL很短(例如5分钟),预热效果很快就过期了。建议预热时配合长TTL策略,让预热的内容有足够长的生存周期。
技巧五:优化回源协议与回源配置,减少源站对缓存策略的干扰
CDN明明缓存了,但源站不给面子
很多时候,CDN节点已经判断可以缓存并存储了响应,但下一次请求却因为源站返回了Cache-Control头中的no-store或private,导致CDN不得不回源校验。罪魁祸首往往是源站Web服务器(Nginx、Apache等)默认的配置。
调优要点:
- 检查源站响应头:使用curl命令查看是否返回了
Cache-Control: no-cache、Pragma: no-cache或Set-Cookie。如果带有Set-Cookie,大部分CDN默认不会缓存该响应(除非你配置了忽略Cookie)。 - 统一配置静态资源的响应头:在Nginx中,通过
location块为静态文件设置add_header Cache-Control "public, max-age=31536000";。确保不携带Set-Cookie。 - 开启CDN的回源跟随能力:部分CDN提供“回源时忽略源站Cache-Control”的开关。如果你明确知道源站的缓存头设置不对但无法立即修改,可以临时开启该开关,强制CDN使用你在控制台配置的缓存策略。但注意,这可能会造成动态内容也被错误缓存。
- 优化回源协议:使用HTTP/2或HTTP/3回源,减少连接建立时间。同时确保回源连接复用,避免频繁创建TCP连接增加延迟。
另外一个小技巧:启用CDN的“分片回源”(Range回源)。当CDN节点存储的是大文件(如视频、安装包)且采用断点续传时,切片回源可以只回源缺失的部分,减少单次回源传输量,间接提升命中率感知。
验证与回滚:调优后的必做动作
每一次调整缓存规则,都有可能引发意料之外的后果。因此建议按以下步骤验证:
- 先在灰度节点测试:把新规则应用到少量CDN节点或特定地域。
- 观察命中率变化曲线:至少监测24小时,对比调整前后的命中率、回源带宽、源站负载。
- 检查可用性:用拨测工具检查关键页面是否正常加载,防止误缓存了过期的动态内容。
- 准备回滚方案:如果命中率反而下降或出现大量异常,立刻恢复原规则。
记住:不要同时调整多个规则,否则无法定位哪个变更起了作用或导致了问题。
总结
CDN缓存命中率的提升不是一蹴而就的,它依赖于对资源类型的精确分类、URL参数的严格管理、动静分离的域名架构、主动预热以及源站响应头的规范化。以上五个技巧,按照优先级排序,你可以先从“过滤URL参数”和“差异化缓存时间”做起,这两步通常能带来最明显的提升。当你把所有静态资源的缓存规则做到极致以后,再考虑预热和动态内容的缓存策略。持续监控并迭代,你会发现CDN带来的加速效果和成本节省远比你想象的要多。
延伸阅读
