CDN节点缓存命中率低于50%,通常不是流量问题,而是缓存规则在打架

CDN缓存命中率低于50%,在生产环境里算是比较明显的异常。除非业务本身就是大量动态接口、强登录态页面、实时查询类请求,否则静态资源、下载包、图片、视频切片这类场景,正常命中率不应该长期趴在这个位置。

实际使用中发现,很多站点接入CDN后,带宽确实走到CDN了,但节点缓存没真正发挥作用。看监控时会发现两个现象:边缘节点回源流量很高,源站CPU、磁盘IO、出口带宽仍然被打满。也就是说,请求经过了CDN,但CDN更像一个转发代理,而不是缓存层。

这种情况一般不是CDN厂商节点不行,更多是配置没设对,或者源站响应头把缓存行为“否掉”了。下面按排查顺序说,生产上基本也是这么看。

Cache-Control把CDN缓存直接禁掉了

命中率低时,先抓响应头,不要先改CDN配置。用curl看一下源站返回什么:

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

如果看到这些头,就要警惕:

Cache-Control: no-cache

Cache-Control: no-store

Cache-Control: private

Pragma: no-cache

Expires: 0

这里补充一点,no-cache这个名字很容易误导。它不是完全不缓存,而是每次使用缓存前都要向源站验证。很多CDN在默认策略下遇到no-cache会选择不缓存,或者缓存后频繁回源校验,命中率自然不好看。

no-store更直接,意思是不允许存储。private通常表示只给浏览器这类私有缓存使用,不适合共享缓存,CDN作为Shared Cache一般会避开。

常见问题是后端框架统一加了防缓存头,连图片、CSS、JS、字体文件都一起带上。比如Java网关、Nginx模板、Node中间件里一刀切写了:

add_header Cache-Control "no-cache, no-store, must-revalidate";

这类配置对登录页、订单页可能没问题,但放到静态资源目录就是灾难。静态资源应该单独处理,比如:

Cache-Control: public, max-age=2592000

带hash版本号的资源可以更激进:

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

生产上比较稳的做法是按目录分开:/static/、/assets/、/uploads/public/给较长缓存;/api/、/user/、/admin/不缓存或短缓存。不要指望一条全站缓存规则解决所有路径。

CDN缓存规则优先级写反了

很多CDN控制台都有“缓存规则”“路径规则”“文件后缀规则”。问题经常出在优先级。

比如配置长这样:

规则A:/* 缓存0秒

规则B:*.jpg 缓存30天

规则C:/static/* 缓存7天

看起来jpg和static都设了缓存,但如果规则A优先级更高,所有请求都会命中“缓存0秒”。控制台上你能看到规则存在,但实际不会生效。

不同CDN厂商的匹配方式不完全一样,有的是列表靠上优先,有的是精确路径优先,有的是后创建覆盖前创建。这个地方不要凭感觉,要看控制台说明,或者直接用测试URL验证。

比较容易出错的还有这种:

/api/* 不缓存

/* 缓存10分钟

如果优先级错了,API可能被缓存;反过来,如果/*不缓存压住了静态规则,静态资源又不缓存。实际排查时不要只看“有没有配置”,要看“最后生效的是哪条”。

URL参数没有归一化,导致同一个文件被当成大量不同对象

缓存命中率低于50%,URL Query是高频原因。

比如同一张图片:

https://cdn.example.com/img/logo.png?v=1

https://cdn.example.com/img/logo.png?v=2

https://cdn.example.com/img/logo.png?from=home

https://cdn.example.com/img/logo.png?utm_source=ad

CDN默认通常会把完整URL作为缓存Key,Query不同就认为是不同资源。业务侧如果给静态资源拼了时间戳、随机数、渠道参数,节点缓存会被拆得很碎。

最典型的是前端为了“防缓存”,写成:

/static/app.js?t=1699999999

每次发布甚至每次请求都变,CDN没有复用空间。用户A请求一次,用户B请求的是另一个URL,节点只能继续回源。

这里要区分两类参数。

版本参数可以保留,比如v=20240501、hash=abc123,因为它代表资源版本。统计参数、渠道参数、随机参数,通常应该从缓存Key里忽略掉,比如utm_source、utm_medium、from、spm、timestamp。

CDN侧一般有“忽略全部参数”“保留指定参数”“忽略指定参数”这类能力。静态资源建议做缓存Key归一化:图片、CSS、JS、字体、安装包可以忽略无关参数;API不要乱忽略参数,否则可能返回错数据。

Set-Cookie让CDN误判成私有内容

还有一种很隐蔽:静态资源响应里带Set-Cookie。

比如访问一张图片,响应头里出现:

Set-Cookie: sessionid=xxxxx; Path=/; HttpOnly

这在业务里不一定有人注意,因为浏览器能正常加载,页面也没报错。但对CDN来说,带Set-Cookie的响应经常被认为和用户状态有关,默认不缓存或谨慎缓存。

实际使用中发现,有些站点是入口网关统一写Cookie,静态目录也没排除。还有一些WAF、安全插件、A/B测试系统,会在所有响应里塞Cookie。结果就是图片、JS、CSS全部变成“疑似私有响应”。

静态资源域名最好和主站业务域名拆开。比如:

www.example.com 用于页面和接口

static.example.com 用于图片、CSS、JS、下载文件

static域名不要写业务Cookie,不要做登录态判断,也不要经过会话中间件。这样CDN缓存策略会清爽很多。

源站返回状态码不适合缓存

CDN并不是所有状态码都默认缓存。200一般没问题,301、302、404、500这类要看配置。命中率低时,要看回源日志里状态码分布。

如果大量请求返回302,CDN可能不缓存跳转结果。比如图片访问先跳到鉴权地址、对象存储地址、临时签名地址,节点每次都要回源拿跳转。

如果大量404没有缓存,爬虫扫不存在路径时会把源站打得很难受。实际线上经常看到这种:正常用户访问量不大,但各种扫描器、爬虫请求不存在的图片、旧路径、备份文件,全部穿透到源站。404不缓存时,命中率会被拉低,源站也会被拖累。

对静态站点来说,404可以设置一个短缓存,比如30秒、60秒、5分钟,视业务而定。这样同一个不存在资源不会持续打源站。500、502、503这类错误码要谨慎缓存,一般不建议长缓存,否则故障页面会被节点放大。

缓存时间太短,节点还没复用就过期了

命中率低有时不是完全没缓存,而是缓存TTL太短。

比如静态资源缓存60秒,看起来配置了缓存,但如果用户请求分散、节点多、区域多,每个节点还没积累几次访问就过期了。特别是长尾图片、小文件很多的网站,TTL短会让命中率非常难看。

可以粗略看这个关系:

高频文件:缓存1小时到30天,命中率提升明显

低频长尾文件:TTL太短基本没意义,建议结合业务设到7天、30天

频繁更新但带文件名hash:可以设365天

不带版本号且会覆盖更新:缓存时间要保守,或者发布时主动刷新CDN

多说一句,文件名带hash是CDN缓存最舒服的方式。比如app.8f3a1c.js,内容变了文件名也变,旧文件缓存一年都没关系。不要用固定app.js然后每次覆盖,再靠手动刷新救场,这种方式容易出事故。

动态接口和静态资源混在一个域名下

CDN命中率统计通常按域名看。如果一个域名里既有图片、JS、CSS,又有大量/api/接口,请求量一混,命中率会被动态请求拉低。

比如一个电商站:

/static/ 命中率90%

/upload/ 命中率85%

/api/ 命中率0%

/search/ 命中率0%

如果/api/请求占总请求数60%,全站命中率可能就掉到40%左右。这个时候不是静态缓存没做好,而是统计口径被动态请求稀释了。

建议按域名或路径拆监控。静态资源域名单独看命中率,API域名单独看回源和延迟。生产排障时,如果只盯全站平均值,很容易误判。

有些业务还会把CDN当反向代理用,所有流量都接进来。这没问题,但缓存命中率就不能按静态站点标准要求。要拆开看:哪些路径应该缓存,哪些路径本来就不该缓存。

Range请求没处理好,视频和大文件命中率会很差

下载、音视频、安装包这类场景,要特别看Range请求。

浏览器和播放器经常发:

Range: bytes=0-1023

Range: bytes=1048576-2097151

如果CDN没有开启Range回源、分片缓存,或者源站不支持Range,节点缓存效果会很差。大文件每次都整文件回源,或者不同Range片段无法复用,命中率和回源带宽都会难看。

源站应该正确返回206 Partial Content,并带上:

Accept-Ranges: bytes

Content-Range: bytes 0-1023/10485760

CDN侧要确认是否支持分片缓存、Range回源、视频拖拽。尤其是MP4、HLS、APK、ZIP这些文件,不要只看200请求,206请求的占比和命中率更关键。

Vary头把缓存Key拆得太细

Vary是HTTP缓存里很容易被忽略的头。比如:

Vary: Accept-Encoding

这个很常见,也合理,因为gzip、br、未压缩版本确实不同。

但如果返回:

Vary: User-Agent

Vary: Cookie

Vary: *

缓存对象会被拆得非常碎。User-Agent变化太多,Cookie更夸张,每个用户可能都不一样。结果同一个URL在CDN里变成很多份缓存,命中率下降。

实际排查中,Vary: Cookie经常出现在后端框架或代理层默认配置里。静态资源不应该根据Cookie变化,能去掉就去掉。移动端和PC端如果确实返回不同内容,建议用不同URL或不同域名,不要靠User-Agent隐式分支。

源站压缩策略和CDN压缩策略冲突

压缩也会影响缓存命中。比如源站有时返回gzip,有时返回br,有时返回未压缩,CDN再叠加自动压缩,如果缓存Key和响应头处理不一致,就可能出现重复缓存、回源增多,甚至内容编码错误。

常见做法是让CDN统一处理压缩,源站保持稳定输出;或者源站明确按Accept-Encoding返回,并正确设置Vary: Accept-Encoding。不要一部分路径源站压缩,一部分路径CDN压缩,规则还不一致。

CSS、JS、HTML适合压缩,图片、视频、ZIP、APK这类已经压缩过的文件没必要再压。压缩策略本身不是命中率低的最大原因,但在高并发站点里,它会让缓存对象数量变多,排查时别漏掉。

缓存刷新太频繁,把节点缓存一直清空

有些团队发布系统做得很“勤快”,每次发布都全站刷新CDN。一天发布十几次,每次刷新/*,节点缓存刚热起来就被清掉。监控上看,命中率会周期性下跌,回源带宽出现尖峰。

CDN刷新要尽量精准。改了哪个文件刷哪个文件;改了目录再刷目录;非必要不要全站刷新。带hash的前端资源甚至不需要刷新,因为新文件名就是新资源,旧资源留在节点上也不影响访问。

刷新和预热也要配合。大文件、活动页、游戏安装包、补丁包发布前,可以先做预热,让热点节点提前拉取。否则活动一开始,所有用户同时触发回源,源站出口可能直接顶满。

如果源站本身扛不住突发回源,选择服务器时也要按峰值考虑。比如游戏更新包、活动素材、海外下载这类业务,除了CDN策略,源站带宽和防护也要跟上。如果你也在找这种能配合CDN做源站承载、高防和海外访问的机器,可以看看129云,他们有宁波高防、美国高防和中国澳门大宽带这类产品线,适合游戏、企业站、高防和海外分发场景,咨询可以打400-9177118。

缓存Key没有把协议、Host、大小写规则想清楚

缓存Key不是只有URL路径。协议、Host、Query、Header都可能参与。

比如同一个资源,同时存在:

http://static.example.com/a.jpg

https://static.example.com/a.jpg

https://www.example.com/a.jpg

https://cdn.example.com/a.jpg

如果没有做301规范化或缓存Key统一,CDN可能认为这是不同对象。用户访问入口越乱,缓存越分散。

大小写也要注意。Linux源站区分大小写,CDN一般也按路径精确缓存:

/Image/logo.png

/image/logo.png

这两个可能是两个缓存对象。业务侧链接不规范时,命中率会被拆散。对于静态资源,最好在生成URL时统一小写、统一域名、统一HTTPS。

回源Host配置不对,导致源站返回了不可缓存内容

CDN回源时会带Host。这个Host如果配置错了,源站可能返回默认站点、跳转页、鉴权页,或者带了错误的Cache-Control。

比如用户访问:

https://static.example.com/logo.png

CDN回源到源站IP时,Host却带成:

www.example.com

Nginx虚拟主机匹配到主站配置,返回的是带Cookie和no-cache的响应。前端看起来图片能打开,但CDN就是不缓存,或者缓存了错误内容。

排查时要看CDN的回源Host配置,也要在源站Nginx日志里打印$host、$http_host、$uri、$upstream_cache_status这类字段。不要只看客户端访问结果。

鉴权URL和临时签名让缓存复用变差

防盗链、Token鉴权、临时签名在下载和视频业务里很常见,但配置不当会明显影响命中率。

比如每个用户拿到的URL都不同:

/video/a.mp4?token=userA_1710000001

/video/a.mp4?token=userB_1710000002

如果token参与缓存Key,同一个视频会被拆成无数份。正确做法通常是:CDN边缘校验token,但缓存Key忽略token参数。这样安全校验还在,缓存对象仍然是/video/a.mp4。

这里要非常小心,不是所有鉴权参数都能忽略。如果参数会决定返回内容,比如清晰度、语言、版本,就不能随便从缓存Key里剔除。可以忽略的是只用于访问控制、不改变文件内容的参数。

源站没有区分可缓存和不可缓存的Content-Type

CDN缓存策略经常按文件后缀写,但有些业务URL没有后缀:

/image/12345

/file/download/abc

/avatar/u/10001

这些URL实际返回image/jpeg、application/octet-stream,但因为没有.jpg、.zip后缀,CDN后缀规则匹配不到,走了默认不缓存。

遇到这类业务,要么按路径规则配置,比如/image/*缓存30天;要么按Content-Type配置缓存,如果CDN支持的话。不要只依赖文件扩展名。

头像类资源还要看是否会覆盖更新。如果/avatar/u/10001永远是同一个URL,但用户换头像后内容会变,长缓存会导致旧头像残留。更好的方式是URL带版本:

/avatar/u/10001?v=20240530

或者生成新文件路径:

/avatar/u/10001/20240530.jpg

边缘节点太分散,低频资源自然命中不高

有一种情况不是配置错误,但容易被误判:资源非常长尾,访问地域分散,单节点请求频次低。

比如有1000万张图片,每张每天只访问1次,用户还分布在全国甚至全球。即使每张图片缓存30天,首次访问也必然回源。节点越多,每个节点命中同一资源的概率越低。

这种场景要看“热点资源命中率”和“回源带宽占比”,不要只盯全量请求命中率。长尾资源会拉低请求命中率,但如果热点大文件、热门图片命中高,带宽节省仍然明显。

可以把资源分层处理:

热门资源:发布后预热,长TTL

普通资源:按目录长缓存

长尾资源:接受首次回源,控制源站并发和带宽

无效请求:404短缓存,减少穿透

排查时别只看一个“命中率”数字

CDN控制台里常见两个指标:请求命中率和流量命中率。

请求命中率低,表示很多请求回源;流量命中率低,表示大量字节回源。两者含义不同。

一个页面有很多小API请求和少量大图片,可能请求命中率只有40%,但流量命中率有90%。这说明大文件缓存住了,小动态请求没缓存,源站带宽压力可能不大。

反过来,请求命中率80%,流量命中率30%,就要警惕大文件没缓存。比如视频、安装包、补丁包都在回源,小图标和CSS倒是命中了,看起来请求数字好看,但源站出口还是被打满。

生产排查建议按这些维度切:

按路径:/static/、/api/、/upload/、/video/分开看

按状态码:200、206、301、302、404分开看

按文件类型:图片、JS、CSS、视频、下载包分开看

按地域和运营商:BGP、CN2、GIA线路访问质量和回源分布分开看

按Top URL:看前100个回源URL到底是什么

一个真实排查顺序可以这么走

拿到“命中率低于50%”这个问题,不建议直接改缓存时间。比较稳的排查顺序是:

先取Top回源URL,看是静态资源、API、404还是大文件。

再curl这些URL,看Cache-Control、Set-Cookie、Vary、Content-Type、状态码。

然后看CDN规则命中情况,确认路径规则、后缀规则、Query处理、缓存Key配置没有互相覆盖。

接着看源站日志,确认CDN回源Host、状态码、Range请求是否正常。

如果是发布后命中率掉,查刷新记录和预热记录。很多时候不是缓存策略坏了,是刚被全站刷新。

如果是活动期间掉,查新增URL是否带随机参数、Token是否参与缓存Key、热点文件有没有预热。

几个常见场景的配置取值

静态JS/CSS,文件名带hash:Cache-Control建议public, max-age=31536000, immutable,CDN缓存30天到365天。

静态JS/CSS,文件名不带hash:CDN缓存10分钟到1小时,发布时精准刷新,不建议长期缓存。

图片资源,URL不变且可能覆盖:缓存1小时到1天,看业务可接受的更新延迟。

图片资源,URL带版本或新路径:缓存7天到30天,热门资源可以更长。

视频和大文件:开启Range回源、分片缓存,TTL按7天到30天配置,发布前预热。

API接口:默认不缓存。确实可缓存的公共接口,比如配置项、地区列表、公告列表,可以单独设10秒到5分钟,并确认不带用户私有数据。

404响应:静态资源场景可以缓存30秒到5分钟,降低扫描和重复无效请求对源站的压力。

命中率低于50%时,最值得先看的响应头

Cache-Control:看有没有no-store、private、no-cache,或者max-age太短。

Set-Cookie:静态资源不应该随便带。

Vary:Accept-Encoding正常,Cookie、User-Agent要谨慎。

Content-Type:确认无后缀URL是不是被识别成可缓存资源。

Status Code:重点看200、206、302、404比例。

Age:如果CDN返回Age头,可以看缓存对象在节点上存活了多久。Age一直没有或一直很小,说明缓存没留下来或频繁过期。

X-Cache、CF-Cache-Status、X-Cache-Status这类厂商自定义头也要看。HIT、MISS、BYPASS、EXPIRED、REVALIDATED含义不同,BYPASS通常说明规则或响应头让CDN绕过缓存,EXPIRED说明缓存过期后回源,MISS说明节点没有对象。

源站能力也要跟CDN缓存策略匹配

CDN不是把源站压力变没,而是把可复用内容挡在边缘。只要有MISS、刷新、预热、回源校验,源站就还要承接流量。命中率从40%调到85%之前,源站可能会经历几轮回源尖峰。

如果源站带宽只有10Mbps,却在做大文件分发,哪怕CDN策略最终能跑起来,预热和首次访问也可能把源站打满。游戏补丁、APP安装包、视频素材、海外下载这类业务,源站建议准备更高出口和更稳的线路。

比如国内业务还要考虑DDoS风险,可以选高防源站;海外用户多,要看BGP、CN2、GIA等线路质量;澳门、美国节点适合不同访问人群。129云的宁波高防-特惠有100Gbps防御,美国高防-B型有200G防御,中国澳门大宽带适合需要1-10Gbps峰值带宽但不强依赖大陆访问质量的场景,选源站时可以结合CDN回源区域一起评估。

命中率一直上不去时,重点盯这几类配置

全站Cache-Control是否一刀切成no-cache或no-store。

CDN缓存规则优先级是否被/*默认规则覆盖。

Query参数是否导致缓存Key碎片化,尤其是timestamp、utm、token。

静态资源是否带Set-Cookie、Vary: Cookie、Vary: User-Agent。

302、404、206这类状态码是否没有对应缓存策略。

发布系统是否频繁全站刷新。

动态接口是否和静态资源混在同一个命中率统计里。

Range回源、分片缓存是否开启,源站是否正确支持206。

回源Host是否命中正确的源站虚拟主机。

无后缀资源是否因为匹配不到后缀规则而没缓存。