CDN节点缓存命中率低,先别急着加节点

CDN缓存命中率低,表面看是节点没有缓存住资源,实际排查时经常会牵出源站、URL、Header、业务发布、用户访问分布、缓存策略这些问题。实际使用中发现,很多团队一看到命中率从 90% 掉到 60%,第一反应是怀疑 CDN 厂商节点质量不行,但真正抓日志看下来,常见原因反而是业务侧把缓存打散了。

缓存命中率一般看两个指标:请求命中率和字节命中率。请求命中率高,不代表带宽省得多;字节命中率低,说明大文件可能一直回源。比如一个站点小图片命中率 95%,但视频、安装包、静态包持续回源,账单和源站压力照样难看。

排查时不要只盯控制台上的一个百分比,最好把 CDN日志、源站 access log、URL维度排行、状态码分布、回源流量曲线放在一起看。命中率低的时候,先确认到底是“缓存不了”,还是“缓存了但很快过期”,或者“缓存键被打散”。

URL是否被参数打散

这是最常见的问题。CDN默认会把完整 URL 作为缓存键,很多业务请求里带了大量参数,比如:

/static/app.js?v=20240501
/static/app.js?v=20240502
/static/app.js?timestamp=1717000000
/static/app.js?from=ad01

如果这些参数对资源内容没有影响,CDN仍然按不同 URL 缓存,就会出现同一个文件被缓存成很多份。访问量一分散,每份缓存热度都不够,边缘节点很难稳定命中。

实际案例里,一个 H5 活动页的 JS 和 CSS 都带 timestamp 参数,用户每次打开页面参数都变,CDN请求命中率只有 42% 左右。把 timestamp 参数从缓存键里忽略,只保留真正影响内容版本的 v 参数,命中率当天就回到 88% 以上。

参数处理不要一刀切

这里补充一点,忽略参数不是把所有 query string 都干掉。比如图片处理服务常见这种 URL:

/image/a.jpg?width=300&height=300
/image/a.jpg?width=800&height=800

这种参数会影响资源内容,不能简单忽略。不然 300px 的图和 800px 的图可能被当成同一份缓存,用户拿到错误内容。

更稳的做法是按目录或文件类型拆策略。静态 JS、CSS 可以忽略无意义参数;图片裁剪参数要保留;API接口一般不缓存,除非业务明确允许。

Cache-Control和Expires是否把CDN拦住了

CDN缓存不是只看控制台配置,源站返回的 Header 也会影响缓存行为。经常见到源站返回这些 Header:

Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: private
Set-Cookie: sessionid=xxxx

这些 Header 很容易让 CDN 不缓存,或者缓存时间非常短。尤其是 Set-Cookie,很多框架默认给所有响应都带 Cookie,哪怕是 /static/logo.png 这种静态资源也带。部分 CDN 策略下,带 Set-Cookie 的响应会被判定为用户相关内容,直接不缓存。

实际使用中发现,命中率低于 50% 的站点,Header问题占比很高。排查方式很简单,用 curl 看一眼:

curl -I https://example.com/static/app.js

重点看 Cache-Control、Expires、ETag、Last-Modified、Set-Cookie、Vary。如果静态资源返回 private 或 no-store,CDN控制台里配再长 TTL 也可能不生效,或者行为和预期不一致。

静态资源建议给明确的缓存时间

带 hash 的静态资源可以给长缓存,比如:

Cache-Control: public, max-age=31536000, immutable

例如 /assets/app.8f3a9c.js 这种文件名里带内容 hash,内容变化时文件名也变化,缓存一年问题不大。用户再次访问时直接命中边缘节点,CDN也不用频繁回源确认。

不带 hash 的文件就谨慎一点,比如 /static/app.js 这种名字固定的资源,可以设置 1 小时、6 小时、1 天,再配合发布时主动刷新。不要既想长缓存,又用固定文件名覆盖发布,这会把线上更新变成玄学问题。

缓存时间太短,节点刚热起来就过期

有些业务把所有静态资源 TTL 配成 60 秒,看起来很安全,实际效果是 CDN一直在回源。对于访问量不算特别大的业务,边缘节点可能刚缓存完,一分钟后又过期,下一批用户继续回源。

可以简单看一个场景:某资源在一个边缘节点每 5 分钟只有 20 次请求,TTL 设置 60 秒时,大量请求会触发回源校验;TTL 设置 1 小时后,只要资源不频繁变更,同节点命中会明显提升。

常见资源的 TTL 可以按变化频率拆开:

HTML:首页、活动页入口,通常 30 秒到 5 分钟,看发布频率和业务容忍度。
JS/CSS:带 hash 的版本文件,7 天到 365 天。
图片:头像类看更新机制,商品图、内容图一般 1 天到 30 天。
视频、安装包、补丁包:7 天到 365 天,发布新版本走新 URL。
API:默认不缓存,明确读多写少且允许延迟的数据再单独配置。

多说一句,TTL不是越长越好。长缓存的前提是资源版本管理清楚。文件名不变、内容直接覆盖,再配长缓存,后面排查用户看到旧内容会很麻烦。

发布方式会直接影响命中率

很多命中率波动发生在发版之后。比如前端每次构建都会生成全新的文件名,即使内容没变化也换 hash,CDN会认为全是新资源,老缓存立即失去价值。短时间内用户集中访问新版本,边缘节点开始大量回源,命中率自然下降。

这种情况在大型前端项目里很明显。一次发布改了一个页面,但构建工具把公共 chunk、vendor、runtime 全部重新生成,结果全站静态资源 URL 都变。CDN日志里看,TOP URL 全是新文件,老缓存没有机会复用。

优化思路是让构建产物稳定。没有内容变化的文件不要换 hash;公共依赖尽量拆出来;发版前可以预热核心资源。预热不是万能,但对于大文件、首页关键资源、热门区域节点,效果很直接。

预热和刷新要分清

刷新是让 CDN删除或失效旧缓存,下一次请求回源取新内容。预热是提前让 CDN节点去源站拉资源,用户访问时直接命中。

如果每次发布都全站刷新,命中率会被自己打掉。更合理的是只刷新变更文件,或者使用带 hash 的文件名避免刷新静态资源。HTML入口页刷新即可,JS/CSS/图片通过新 URL 自然生效。

Vary Header可能把缓存拆成很多份

Vary经常被忽略。比如源站返回:

Vary: Accept-Encoding

这个很正常,gzip、br、未压缩内容可能分别缓存。问题出在:

Vary: User-Agent
Vary: Cookie
Vary: Accept-Language

如果 CDN按 User-Agent 或 Cookie 维度缓存,一个 URL 可能被拆成大量缓存副本。用户越多、设备越杂,缓存越分散。尤其是 Vary: Cookie,基本会把公共缓存打碎。

移动端适配不要轻易依赖 User-Agent 让同一个 URL 返回不同内容。更可控的方式是分 URL,比如 /m/ 和 /pc/,或者由前端自适应。CDN层面的缓存最怕“同一个 URL 在不同条件下返回不同内容”。

源站状态码和回源异常也会拉低命中

有些命中率低不是缓存策略问题,而是源站经常返回 403、404、500、502。CDN对错误状态码的缓存策略通常比较保守,或者缓存时间很短。结果就是异常请求反复打到源站。

建议把 CDN日志按状态码拆开看:200、206、301、302、304、403、404、5xx。视频、下载类业务还要重点看 206 Partial Content。Range请求如果处理不好,大文件会频繁回源,字节命中率很差。

实际遇到过一个下载站,请求命中率 80% 看起来还行,但字节命中率只有 35%。原因是大文件 Range回源比例太高,节点没有完整缓存文件,用户下载时大量分片都回源。调整 Range缓存策略并对热门安装包做预热后,字节命中率拉到 82% 左右,源站出口带宽下降非常明显。

冷热资源混在一起,缓存效果会被稀释

CDN节点缓存空间不是无限的。热门资源会留下,冷门资源会被淘汰。资源站点如果 URL数量巨大、访问分布很散,命中率天然会低一些。

比如图片站、UGC社区、短视频封面、用户头像这类业务,资源数量可能是千万级,但每个资源访问次数很少。即使 TTL 配得很长,节点也可能因为空间淘汰掉冷门对象。控制台上看到命中率不高,不一定是配置错了,而是访问模型决定的。

这种场景可以按资源热度分层。热门内容走 CDN长缓存和预热;长尾内容接受较低命中率;极冷资源可以考虑对象存储直出或低成本回源。不要为了追求一个统一命中率,把所有资源都按热门资源处理。

区域流量不均,部分节点一直不热

CDN命中率还和用户分布有关。一个资源在华东访问量很大,华南访问很少,华东节点命中率可能 95%,华南节点只有 40%。全局平均值会掩盖问题。

排查时建议按区域、运营商、节点维度看。尤其是跨境业务,用户在中国大陆,源站在海外,如果 CDN节点没有命中,就会回源走国际链路,延迟和失败率都容易上来。

如果源站本身放在海外,回源质量也要关注。比如香港、韩国、美国这些节点做源站时,线路差异会影响回源速度。业务面向国内用户,香港 CN2、精品线路、回国优化这类配置会更稳一些。如果你也在找这种海外源站或回源节点,可以看看129云,像香港CN2大宽带-E型适合做跨境业务源站,50Mbps峰值带宽、精品CN2、双向流量;韩国-E型带傲盾防火墙和回国优化,更偏游戏、企业访问场景;美国活动机适合预算敏感、访问量中等的海外业务。需要确认线路和防护参数时,可以直接打客服热线400-9177118。

动态请求不要硬塞进CDN缓存

有些团队为了提升命中率,把接口也配成缓存,短期看命中率上去了,后面数据一致性问题会更麻烦。用户订单、账户信息、权限状态、购物车、后台接口,这些不应该走公共缓存。

但也不是所有 API 都不能缓存。比如榜单、配置、地区列表、文章详情、商品基础信息,读多写少且允许几十秒延迟,可以按业务维度做短缓存。关键是要明确哪些接口允许缓存,哪些接口必须绕过 CDN。

接口缓存要特别注意 Cookie、Authorization Header、用户态参数。带 Authorization 的请求如果被公共缓存错误处理,风险很高。CDN规则里通常要对 /api/user、/api/order、/admin 这类路径直接 bypass。

压缩和内容协商也会影响命中

开启 gzip、br 压缩后,CDN可能会按 Accept-Encoding 缓存不同版本。正常情况下问题不大,但如果源站压缩策略混乱,同一个 URL 有时返回 gzip,有时返回 br,有时返回未压缩,命中率和用户体验都会波动。

建议压缩策略放在 CDN侧统一处理,源站尽量稳定返回原始内容或固定压缩行为。JS、CSS、HTML、JSON 适合压缩;图片、视频、zip、apk 这类已经压缩过的内容,再压缩意义不大,还会浪费 CPU。

这里还有一个容易被忽略的细节:如果源站返回的 Content-Length、ETag 和压缩版本不匹配,某些客户端或中间层会出现下载异常。大文件分发时尤其要谨慎。

缓存规则的匹配顺序要检查

CDN控制台里经常会配置多条规则,比如按域名、目录、文件后缀、Header、参数匹配。规则多了以后,命中哪条规则不一定符合直觉。

常见误配置是:上面有一条 /* TTL 60秒,下面才是 /static/* TTL 30天,但实际优先命中了 /*。或者 .jpg 配了长缓存,但 /user/avatar/* 又被另一条规则覆盖成不缓存。

排查时可以拿几个典型 URL 做测试:HTML入口、JS、CSS、图片、视频、接口、下载包。每类资源抓一次响应 Header,再对照 CDN规则。不要只看控制台上“已经配置了长缓存”,要确认请求真的命中了那条规则。

看命中率要拆请求命中和字节命中

请求命中率适合看小文件、页面资源;字节命中率更能反映带宽节省效果。下载、视频、游戏补丁、安装包业务,重点看字节命中率。

举个实际场景:一个游戏补丁站,每天请求量不算大,但单个补丁 500MB 到 2GB。如果小图标、配置文件都命中,大补丁不命中,请求命中率可能还有 70%,但源站带宽照样被打满。

这类业务要把大文件单独拎出来看。热门补丁预热到主要区域节点,TTL拉长,URL版本化,Range缓存确认生效。源站带宽也不能太小,预热和回源高峰时如果源站扛不住,CDN节点拉取失败,后面用户还是会慢。

命中率突然下降时的排查顺序

线上突发问题时,不建议从复杂配置开始猜。可以按时间线查:最近有没有发版、有没有刷新全站缓存、有没有改 Header、有没有切源站、有没有新增参数、有没有活动流量进入、有没有 DDoS 或异常爬虫。

如果命中率下降同时回源流量上升,重点看缓存失效、URL变化、TTL变短。如果命中率下降但总请求量暴涨,可能是新用户区域带来的冷启动,也可能是爬虫在扫长尾 URL。如果 5xx 上升,要先看源站健康和回源链路。

异常爬虫也会拉低命中率。爬虫喜欢构造大量随机 URL,比如 /product/随机ID、/search?keyword=随机词,这些请求很难命中缓存,还会把源站拖慢。可以结合 WAF、频率限制、Bot识别、Referer规则处理,不要让 CDN缓存空间被垃圾请求污染。

源站能力不能太弱

CDN不是源站的替代品。缓存命中率再高,也会有回源:缓存过期、首次访问、刷新后访问、节点冷启动、Range拉取、异常状态码处理。源站带宽、磁盘 IO、Web服务并发都要能扛住回源峰值。

尤其是刚发布大文件、做活动页、游戏更新包上线时,CDN预热会集中拉源。如果源站只有很小带宽,预热任务本身就可能失败。这个时候选择源站机器要看带宽、线路、防护和稳定性,不只是 CPU 内存。

跨境分发场景里,源站线路更敏感。香港 CN2、韩国回国优化、美国精品线路的差异,在回源延迟上会直接体现出来。业务如果还叠加攻击风险,高防服务器和DDoS防护也要提前考虑,不然缓存未命中的请求一多,源站很快暴露在压力下。

把缓存策略和业务发布绑在一起

CDN缓存命中率稳定的站点,通常不是靠某个神奇参数,而是发布规范比较清楚。静态文件带 hash,HTML短缓存,接口按路径绕过或短缓存,大文件版本化,发布后只刷新入口和变更资源,热门资源提前预热。

这里不要让运维单独背锅。前端构建、后端 Header、CDN规则、源站线路都会影响命中率。比如后端框架升级后默认给静态资源加了 Set-Cookie,前端构建改了 hash策略,CDN侧看到的就是命中率下降。

比较建议在发布检查里加几项固定校验:核心静态资源是否带 hash,Cache-Control 是否符合预期,Set-Cookie 是否误加到静态资源,是否触发全站刷新,预热 URL 是否覆盖大文件和首页关键资源。用脚本跑 curl 就能发现不少问题。

常见配置参考

HTML入口页:TTL 30秒到300秒,发布时刷新指定 URL,避免全站刷新。

带 hash 的 JS/CSS:TTL 30天到365天,Cache-Control 使用 public 和 immutable,文件内容变化必须换文件名。

不带 hash 的 JS/CSS:TTL 不宜太长,建议配合发布刷新,避免用户长时间拿旧版本。

图片资源:商品图、文章图、素材图可以 1天到30天;头像看业务更新频率,更新后可刷新单个 URL。

视频和下载包:TTL 7天到365天,开启 Range缓存能力,热门文件做预热,源站带宽要能承受预热峰值。

API接口:用户相关接口 bypass;公共配置、榜单、详情页数据可以短缓存,注意 Cookie 和 Authorization。

错误状态码:404可以短缓存几十秒到几分钟,防止恶意扫描打爆源站;5xx一般谨慎缓存,避免把临时故障放大给用户。

日志里重点看这些字段

CDN命中状态:HIT、MISS、EXPIRED、BYPASS、REVALIDATED,不同厂商命名略有差异,但含义接近。

URL和query string:看是否存在随机参数、时间戳参数、广告追踪参数、无意义参数。

状态码:200和206占比、404异常增长、5xx是否和回源峰值重合。

回源耗时:MISS时回源慢,会拖慢首个用户访问,也会影响节点填充缓存。

文件大小:大文件不命中对字节命中率影响最大,别被小文件请求量掩盖。

区域和运营商:看是否某个区域、某个 ISP 命中率异常低,BGP、CN2、GIA、普通国际线路在跨境链路上的表现差异会比较明显。

不要只追求控制台上的高命中率

命中率优化的目标是减少不必要回源、降低源站压力、缩短用户访问延迟。为了把数字做高,把不该缓存的用户数据缓存住,或者把频繁变更的内容长时间缓存,后面会引发更大的线上问题。

有些业务命中率做到 85% 就很健康,有些下载业务字节命中率低于 90% 就会让源站带宽很紧张。判断是否需要继续优化,要结合资源类型、访问分布、源站成本、用户延迟一起看。

如果排查到最后发现 CDN规则没大问题,但每次 MISS 回源都很慢,就该看源站线路和带宽。国内用户访问海外源站,普通国际线路在晚高峰抖动很常见,CN2、GIA、精品BGP这类线路会更适合做回源链路。源站稳定,CDN节点填充缓存才会稳定。