上了 CDN 之后,很多人会盯着一个数字看:命中率。它高的时候九十几,网站又快又省;它低的时候可能只有五六成,结果就是 CDN 花了钱、源站压力没减、用户也没感觉变快。命中率低是 CDN 使用中最常见也最值得优化的问题。
这篇文章先讲命中率到底是什么、怎么算、在哪看,然后逐条拆解命中率低的常见原因——URL 带随机参数、Cookie 和 Vary 导致不缓存、TTL 太短、动态页没静态化、缓存 key 设计不当,每一类都给出对应的优化办法,最后讲怎么建立监控、把命中率长期维持在健康水平。
一、命中率是什么,怎么算怎么看
命中率(Cache Hit Ratio)指的是在所有请求中,由缓存直接命中、不需要回源的比例。
命中率 = 缓存命中请求数 ÷ 总请求数 × 100%
它衡量的是"多少请求被缓存挡下了"。命中率高,说明大部分请求在 CDN 节点就解决了,回源少、源站压力小、用户等待短;命中率低,说明大量请求要回源,CDN 退化成"多绕一跳的中转站"。
不同内容类型的合理命中率不一样,给一组经验参考(实际因站点差异很大):
| 内容类型 | 健康命中率参考 |
|---|---|
| 静态资源(图片、CSS、JS) | 百分之九十以上 |
| 下载类文件 | 百分之九十以上 |
| 视频点播 | 视内容热度,热门可达九成以上 |
| 可缓存 HTML 页面 | 百分之七十以上 |
| 含大量动态接口的站点 | 整体偏低是正常的,要分路径看 |
关键点:命中率要分类看,不能只看一个全站平均值。 一个站点里,静态资源的命中率可能是九十五,动态接口是零,平均下来六十几,看起来很低,但其实该缓存的都缓存好了。所以要按路径、按域名、按文件类型分别统计。
在 CDN 控制台的统计报表里,通常能看到请求数、命中数、回源数、命中率。也可以用响应头自己判断:
# 看单个请求是否命中缓存(字段名各服务商略有不同)
curl -I https://www.example.com/static/app.js
# 关注这些头:
# X-Cache: HIT / MISS
# X-Cache-Status / X-Cache-Lookup
# Age: 内容在缓存中已存活的秒数(>0 基本说明是命中)
# Via: 经过的缓存节点信息
# 连续请求两次同一个资源,看第二次是否 HIT
curl -sI https://www.example.com/static/app.js | grep -i -E 'x-cache|age'
curl -sI https://www.example.com/static/app.js | grep -i -E 'x-cache|age'
二、原因一:URL 带随机参数
这是命中率杀手榜第一名,也是最容易修的问题。
很多系统会在静态资源的 URL 后面加时间戳或随机数,比如 app.js?v=1712345678、logo.png?ts=9988&from=home。这些参数往往是为了"绕过缓存"或者做埋点统计。但对 CDN 来说,URL 只要有任何不同的字符,就被当成一个全新的资源。参数每次都变,就意味着这个资源永远不可能命中缓存,每次都回源。
用户 A:GET /img/banner.png?ts=1712345000 → MISS,回源
用户 B:GET /img/banner.png?ts=1712345100 → MISS,回源(本应命中)
用户 C:GET /img/banner.png?ts=1712345200 → MISS,回源(本应命中)
优化办法有两层:
第一,从源头改掉。埋点统计改用请求头、POST 数据或者独立接口,不要让无意义参数污染静态资源 URL。如果确实需要版本控制,用"内容哈希"做文件名的一部分(app.9f8e7d6c.js),而不是每次随机。
第二,如果改不动业务代码,就在 CDN 侧开启"忽略 URL 参数"。绝大多数 CDN 服务商都支持这个开关,可以把参数从缓存 key 里剔除,让带不同参数的 URL 归一化为同一个资源。
CDN 侧参数处理有三种常见模式:
1. 完全忽略参数:不把任何查询串计入缓存 key(适合纯埋点参数)
2. 保留指定参数:只把指定参数计入 key,其余忽略(推荐,最灵活)
3. 保留全部参数:默认行为,参数不同就不命中
推荐用"保留指定参数":比如你的静态资源真的靠某个参数区分内容(如 ?size=large),就把它列进白名单,其余埋点参数一律忽略。
# 验证参数是否已被忽略:带不同参数请求,若都 HIT 且 Age 一致,说明参数被忽略了
curl -sI "https://www.example.com/logo.png?a=1" | grep -i -E 'x-cache|age'
curl -sI "https://www.example.com/logo.png?b=2" | grep -i -E 'x-cache|age'
三、原因二:Cookie 与 Vary 导致不缓存
第二个高频原因是请求或响应里带了 Cookie。
很多 CDN 的默认策略是"请求带 Cookie 就不缓存",理由是怕把某个用户的私人内容缓存给其他人。这个策略在动态页面上是合理的,但对静态资源来说完全是误伤——一张图片、一个 CSS 文件,内容跟 Cookie 毫无关系,却因为浏览器自动带上了 Cookie 而被判定为不可缓存。
优化办法:对静态资源路径配置"忽略 Cookie",让 CDN 在判断静态资源时不看请求里的 Cookie。
CDN 侧常见配置:
静态域名 static.example.com → 忽略请求 Cookie,正常缓存
主域名 www.example.com → 带 Cookie 的动态请求不缓存
另一个容易被忽略的是 Vary 响应头。Vary 告诉缓存"这个响应因某些请求头而异"。如果响应带 Vary: User-Agent 或者 Vary: Cookie,那缓存会为每个不同的 User-Agent/Cookie 单独存一份,命中率断崖式下跌,缓存空间也会被塞爆。
# 检查响应头里有没有 Vary
curl -sI https://www.example.com/ | grep -i vary
# 如果看到 Vary: User-Agent 或 Vary: Cookie,要特别注意
# 静态资源不应该带这类 Vary
还有个关联问题:如果响应带了 Set-Cookie,有些缓存会认为内容是个性化的,直接不缓存。静态资源响应里不应该出现 Set-Cookie,如果有,通常说明源站配置有问题——比如 Nginx 的某个全局 add_header Set-Cookie 误伤了静态资源。
# 源站 Nginx:确保静态资源不带 Cookie 相关头
location ~* \.(css|js|png|jpg|webp|woff2)$ {
root /var/www/site/static;
expires 365d;
add_header Cache-Control "public, immutable";
# 不要在这里 add_header Set-Cookie
}
四、原因三:TTL 太短
缓存过期时间(TTL)设得太短,内容频繁过期,每次过期后的第一个请求都要回源,命中率自然上不去。
常见错误是给所有资源都用同一个短 TTL,比如全站十分钟。图片、字体、带哈希的 JS/CSS 本来可以缓存一年,十分钟的 TTL 让它们频繁回源,完全没发挥缓存的威力。
正确的做法是按内容类型分层设置 TTL:
| 内容类型 | 建议 TTL | 依据 |
|---|---|---|
| 带内容哈希的文件名 | 一年或更长 | 文件名变了就是新内容 |
| 图片、字体、媒体 | 三十天到一年 | 更新频率极低 |
| 无版本号的 CSS/JS | 几小时到几天 | 权衡更新及时性 |
| 公开可缓存的 HTML | 几分钟到几小时 | 更新较频繁 |
| 动态接口 | 不缓存 | 内容因人而异 |
判断 TTL 是否合理的经验法则:看内容重复访问的间隔。如果一个资源一天被访问一万次、内容一周才更新一次,那 TTL 设一天就足够,回源量从"每次访问都回源"降到"每天每个节点回源一次"。设长 TTL 的前提是"更新时能触发失效",而最省心的失效方式就是文件名版本化。
# 查看源站返回的缓存头是否合理
curl -sI https://www.example.com/static/app.9f8e7d6c.js | grep -i -E 'cache-control|expires'
# 期望:Cache-Control: public, max-age=31536000, immutable
五、原因四:动态页面没有静态化,缓存 key 设计不当
如果你的页面是动态生成的(比如 PHP、Java 渲染的 HTML),默认没法缓存,命中率就会很低。解决办法是"页面静态化"或"页面级/片段级缓存"。
页面静态化:把动态生成的 HTML 输出保存成静态文件,用户访问时直接返回静态文件。适合内容更新不频繁的页面,比如新闻详情页、商品详情页。
片段缓存:页面整体不缓存,但把其中可复用的部分(比如不随用户变化的推荐模块、公共头部)单独缓存,组装时用缓存片段填充。适合页面整体个性化但局部通用的情况。
缓存 key 设计不当:缓存 key 决定了"什么算同一个缓存"。设计得太细,命中率就低。常见的过度设计包括:把 User-Agent 完整纳入 key(导致每个浏览器版本都存一份)、把用户 ID 纳入 key(等于每个用户一份,命中率趋近于零)、把完整的查询串原样纳入 key。
缓存 key 的取舍:
key = URL → 命中率最高,适合无差异内容
key = URL + Host → 默认,适合多站点
key = URL + 指定参数 → 适合靠个别参数区分内容的场景
key = URL + User-Agent → 命中率骤降,除非确实需要
key = URL + 用户 ID → 基本等于不缓存,要避免
设计原则是:只把真正影响内容结果的因素纳入 key,其余一律忽略。
六、原因五:缓存被频繁刷新和来源分散
有些团队为了"确保更新",每次发布都全站刷新 CDN 缓存。全站刷新的代价是:所有节点的缓存同时失效,短时间内海量请求全部回源,命中率瞬间掉到零,源站被打得措手不及。
正确做法是用文件名版本化代替频繁刷新:发布新版本时生成新文件名,旧文件自然被新流量淘汰,不需要刷新。只对极少数特殊情况(比如配置文件、首页 HTML)才手动刷新。
另一个影响命中率的因素是节点分布与调度。如果内容分散在太多节点上,每个节点的访问量都不够大,就很难"攒"出高热度的缓存——同一个资源在几十个节点各存一份,每份的访问频次都很低,命中率自然上不去。这时可以通过分层缓存(边缘节点向中间源取,而非各自回源)来改善。
# 从源站统计回源请求的分布,找出被反复回源的 URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -30
# 出现次数特别多的静态资源,说明它在 CDN 侧没被缓存好
七、怎么监控命中率并持续优化
优化不是一次性的,要建立监控。几个要点:
分维度统计。至少要按域名、按路径前缀、按文件扩展名分别看命中率,才能定位是哪类内容拖了后腿。全站平均值会掩盖问题。
盯回源带宽与回源请求数。命中率低的直接表现就是回源暴涨。把回源带宽作为核心指标,比只看百分比更直观。
设告警。当命中率跌破某个阈值(比如静态资源低于八十五)时告警,及时发现规则被误改或者出现了新的带参数 URL。
定期复盘。每次前端改版、上线新功能后,检查有没有引入新的随机参数、新的 Cookie 依赖。这类问题往往是渐进式引入的。
# 从源站日志粗算各路径的回源请求占比
total=$(wc -l < /var/log/nginx/access.log)
static=$(awk '$7 ~ /\.(css|js|png|jpg|jpeg|webp|woff2)/' /var/log/nginx/access.log | wc -l)
echo "静态资源回源请求数: $static / 总计: $total"
# 找出带查询参数的静态资源请求(命中率杀手)
awk '$7 ~ /\?/ && $7 ~ /\.(css|js|png|jpg)/ {print $7}' /var/log/nginx/access.log | head -20
常见问题
Q:命中率多少算正常?
要分类看。静态资源应该九成以上,下载类九成以上,可缓存 HTML 七成以上。全站平均值意义有限,动态接口多的站点整体偏低是正常的。
Q:开了忽略参数,会不会缓存错内容?
只要确实是"不影响内容"的参数就不会。如果某些参数真的决定返回不同内容(比如 ?size=、?lang=),要把它们加进白名单保留,而不是一律忽略。
Q:为什么同一张图,有的节点命中有的没命中?
节点缓存是独立的,各地节点的访问量和预热情况不同。可以用 CDN 提供的缓存预热功能主动推送内容到节点,或接受"冷节点先回源、热节点命中"的正常差异。
Q:刷新缓存后命中率掉下来正常吗?
正常。刷新后所有节点都要重新回源,短期命中率下降。所以不要频繁刷新,改用文件名版本化。刷新后观察一段时间命中率会回升。
Q:源站设了长缓存,为什么 CDN 还是不缓存?
可能 CDN 侧有覆盖规则,或者请求带了 Cookie、响应带了 Vary/Set-Cookie,或者 CDN 对某些状态码默认不缓存。逐项检查这些开关。
总结
命中率是 CDN 价值的直接体现:静态资源正常应在九成以上,且要按域名和路径分类统计,别被全站平均值误导。
排第一的原因永远是 URL 带随机参数,它让缓存永远无法命中。修法是改掉源头埋点方式,或在 CDN 侧用"保留指定参数"的归一化策略。
Cookie 和 Vary 是第二大杀手:静态资源不该看 Cookie,也不该带 Vary: User-Agent 或 Vary: Cookie,源站要确保静态资源响应不带 Set-Cookie。
TTL 要按内容类型分层设置。带内容哈希的文件名可以设一年,图片字体设几十天到一年,HTML 设几分钟到几小时,动态接口不缓存。长缓存的前提是更新时能失效,而文件名版本化是最省心的失效方式。
动态页面靠静态化或片段缓存来提升可缓存性;缓存 key 只纳入真正影响内容的因素,别把 User-Agent、用户 ID 这类东西塞进 key。
少刷新、多版本化;用分层缓存和预热改善冷节点;建立分维度的监控和告警,把命中率维持在一个健康区间。做到这些,CDN 的钱才算花在了刀刃上。
企业QQ咨询




