CDN节点缓存穿透打垮源站是预热没做好还是规则配错了
CDN节点缓存穿透打垮源站,是预热没做好,还是规则配错了
CDN把源站打穿,这类事故现场通常不复杂:源站 CPU 飙高、Nginx upstream 排队、数据库连接池被占满,CDN控制台看着命中率从 90% 掉到 40%,回源带宽突然抬头。问题是,复盘时很容易被一句“缓存没预热好”带偏。
实际使用中发现,预热只解决一部分问题。更常见的是缓存规则没有覆盖真实请求形态,或者动态参数、Header、Cookie 把本来可缓存的内容切碎了。预热做得再勤,如果规则让 CDN 认为每个 URL 都不一样,源站还是会被打。
先看事故现场,不要急着改配置
缓存穿透不是只有一种表现。很多人看到回源增加就说穿透,其实里面至少有三类情况:缓存未命中、缓存绕过、缓存击穿。排查方向不一样。
| 现象 | 典型表现 | 常见原因 | 处理优先级 |
|---|---|---|---|
| 缓存未命中 | 新 URL、大量冷资源请求,CDN返回 MISS | 预热不足、资源刚发布、TTL太短 | 中 |
| 缓存绕过 | CDN显示 BYPASS,或者命中率异常低 | Cache-Control、Cookie、查询参数、规则优先级配置不当 | 高 |
| 缓存击穿 | 热点资源过期瞬间,大量请求同时回源 | TTL集中失效,没有 stale、回源合并能力弱 | 高 |
一个比较典型的案例:图片站日常 CDN 命中率 96%左右,源站回源带宽 30Mbps。某次活动上线后,命中率掉到 58%,源站回源带宽冲到 700Mbps,后端对象存储网关直接排队。看起来像是活动图没预热,结果抓日志发现,URL 长这样:
/static/banner.jpg?v=20240501&from=wechat&uid=839201
banner.jpg 本来是同一张图,但前端把 uid 拼进了查询参数。CDN默认按完整 URL 缓存,于是每个用户都是一个新缓存键。预热 /static/banner.jpg 没意义,因为真实请求根本不是这个缓存键。
预热没做好时,问题通常长什么样
预热不足的场景有个特点:资源是可缓存的,只是还没进节点。比如游戏安装包、App更新包、视频切片、活动页静态资源,刚发布后用户集中访问,节点没有缓存,只能回源拉。
这种场景里,CDN日志通常能看到大量 MISS,后续同一个 URL 的 HIT 会逐渐上升。源站压力是一个峰值,撑过第一波后会下降。
举个数据更直观。一个 1.8GB 的客户端下载包,活动开始 10 分钟内有 3000 个用户同时下载,如果 CDN边缘节点还没有缓存,理论回源流量会非常夸张。哪怕 CDN有分层缓存,也会把上层节点和源站打得很难看。
这时候预热确实重要,尤其是大文件和热点资源。常见做法是发布完成后先提交预热任务,让核心区域节点提前拉取资源。国内用户为主的业务,要关注华东、华南、华北节点;海外用户为主的业务,要看美国、日本、新加坡、欧洲这些区域是否覆盖。
这里补充一点,预热不是把全站所有 URL 都扔进去。CDN预热接口一般有频控,数量大了也不一定马上生效。更靠谱的方式是把资源分层:
| 资源类型 | 是否建议预热 | 原因 |
|---|---|---|
| 首页 HTML | 看业务 | 如果带用户态,不应强行缓存;纯静态活动页可以预热 |
| JS/CSS | 建议 | 访问集中,体积不算大,命中收益明显 |
| 图片、字体 | 建议 | 高频静态资源,适合长缓存 |
| 安装包、补丁包 | 强烈建议 | 大文件回源成本高,容易把源站带宽打满 |
| API接口 | 谨慎 | 多数接口与用户态、权限、实时数据有关 |
规则配错时,预热会显得很无辜
规则问题更隐蔽,因为控制台里可能已经写了“缓存 30 天”,但真实请求没有命中这条规则,或者被更高优先级规则覆盖了。
经常见到的坑是路径匹配顺序。比如配置了:
/*.jpg 缓存 30 天
/api/* 不缓存
看起来没问题。但如果图片实际路径是:
/api/file/image/123.jpg
那它会命中 /api/* 不缓存。CDN按照规则优先级执行,不会因为后缀是 jpg 就自动帮你判断“这是静态资源”。
还有一种是源站响应头把 CDN配置顶掉。源站返回:
Cache-Control: no-cache, no-store, private
很多 CDN 默认会尊重源站头,哪怕控制台配置了缓存策略,也可能被源站 Header 影响。这个在 Java、PHP、Node.js 框架里很常见,尤其是统一中间件给所有响应都加了 no-cache。
实际排查时不要只看 CDN控制台,要用 curl 看响应头:
curl -I https://www.example.com/static/app.js
重点看这些字段:
Cache-Control、Expires、Set-Cookie、Age、Via、X-Cache、CF-Cache-Status 或 CDN厂商自定义命中字段。
如果静态资源返回了 Set-Cookie,也要警惕。有些 CDN 默认认为带 Cookie 的响应不适合共享缓存,会选择不缓存,或者缓存键里带上 Cookie。结果就是同一张图片因为 Cookie 不同被拆成很多份。
查询参数是缓存穿透的大头
很多缓存穿透不是攻击,是业务自己制造出来的。前端为了埋点、灰度、渠道统计、版本控制,喜欢给静态资源追加参数:
/main.css?v=202405
/logo.png?channel=ios
/video/1.ts?token=xxx&uid=xxx&t=1717000000
参数本身没错,问题是 CDN缓存键怎么处理。
| 参数类型 | 是否影响内容 | 建议处理 |
|---|---|---|
| v、version、hash | 通常影响 | 保留,作为版本缓存键 |
| utm_source、from、channel | 通常不影响 | 忽略,不参与缓存键 |
| uid、user_id | 静态资源一般不影响 | 静态路径下忽略,接口不要乱忽略 |
| token、sign、expires | 可能影响鉴权 | 看鉴权位置,防盗链场景不能简单忽略 |
| timestamp、t | 多数是防缓存参数 | 静态资源里要重点治理 |
多说一句,忽略参数要分路径做,不要全站一刀切。比如 /static/* 可以忽略 utm、from、uid,但 /api/order/detail?id=123 里的 id 绝不能忽略。这个配置错了不是打源站,是直接串数据。
热点过期瞬间回源,比冷资源更危险
缓存击穿经常出现在秒杀页、公告页、游戏配置文件、直播间封面、短视频热门资源上。平时命中率很好,大家以为没问题,TTL 到点后一批节点同时失效,请求一起打回源。
比如一个 JSON 配置文件:
/game/config/server-list.json
TTL 配了 300 秒,日常 QPS 8000。到了第 300 秒,边缘节点缓存过期,短时间内多个节点同时回源。如果源站生成这个 JSON 还要查数据库、查 Redis、做权限判断,后端就会抖。
处理这类问题,光延长 TTL 不一定够。更有效的是看 CDN是否支持:
回源请求合并:同一个缓存键过期时,只放一个请求回源,其余请求等待结果。
stale 缓存:源站异常或刷新期间,允许 CDN先返回旧内容。
TTL 随机抖动:避免大量资源在同一时间点过期。
分层缓存:边缘节点先回上层缓存,上层再回源,减少源站直接压力。
如果 CDN能力不支持这些,源站自己也要做保护。Nginx 可以用 proxy_cache_lock,应用层可以做单飞机制,Redis 可以加互斥锁,别让 1 万个请求同时去重建同一份数据。
源站规格太小,也会把规则问题放大
CDN不是防弹衣,源站基础能力太弱时,一点回源波动就会出事。尤其是海外业务,用国内源站接全球 CDN,跨境回源延迟高,TCP重传、连接堆积都会放大。
实际项目里,源站至少要按“异常回源”估算,而不是按“CDN命中后剩余流量”估算。比如正常命中率 95%,全站峰值 2Gbps,理论回源只有 100Mbps。但规则变更、刷新缓存、活动发布时,命中率可能短时间掉到 70%,回源就是 600Mbps。源站如果只有 100Mbps 带宽,这个时候一定炸。
这里涉及购买和部署时的选择。如果业务在北美、游戏更新包、企业官网加速、活动静态资源这些场景,需要源站本身有稳定带宽和线路,可以看看129云的美国精品网-D型,8C CPU、8G DDR4 ECC、120G SSD、125Mbps峰值带宽,三网精品线路,适合作为海外 CDN源站或静态资源源站。遇到攻击风险更高的活动页、游戏入口页,也可以看美国高防-活动,带 200G 防御,适合把 DDoS 风险和缓存穿透风险一起考虑。需要日本本地访问质量的业务,日本BGP-A型用来做轻量源站、API边缘节点也比较合适。选型不确定可以直接问客服,400-9177118。
别只盯命中率,要拆命中率
CDN控制台里的总命中率容易骗人。一个站点里大文件命中很好,小接口大量回源,总体带宽命中率可能还不错,但请求数命中率很差。源站被打垮看的往往不是带宽,而是请求数、连接数、动态处理能力。
| 指标 | 能说明什么 | 容易误判的地方 |
|---|---|---|
| 带宽命中率 | 大文件、图片、视频是否被缓存 | 大文件命中会掩盖小请求穿透 |
| 请求数命中率 | 源站请求压力是否下降 | 接口、小图、HTML更敏感 |
| 回源 4xx/5xx | 源站是否扛不住或规则异常 | 404也可能被大量穿透放大 |
| TOP URL MISS | 哪些资源没命中 | 需要结合参数归一化后再看 |
| TOP Referer/User-Agent | 是否有爬虫、刷量、异常客户端 | 攻击流量可能伪造正常 UA |
排查时建议从 TOP MISS URL 入手,把 URL 去掉参数后聚合一次。如果去参后发现其实就几十个资源,那基本就是参数导致缓存键碎片化。如果去参后还是几万个不同路径,就要看是不是资源发布策略、爬虫扫描、恶意探测,或者前端生成了不可控路径。
404也要缓存,不然源站会被扫目录拖死
很多站只给 200 响应配置缓存,404、403完全不缓存。结果攻击者或爬虫扫路径:
/wp-admin.php
/backup.zip
/.env
/admin/login
这些请求每次都回源,源站还要做路由匹配、日志写入、WAF检测。QPS 不高时没感觉,一旦被扫到每秒几千请求,后端就开始抖。
静态站、下载站、游戏官网这类业务,可以考虑对 404 做短缓存,比如 30 秒到 5 分钟。这样同一批扫描路径不会反复打源站。注意不要把需要实时创建的资源误缓存,比如用户刚上传文件后马上访问,如果前一秒被缓存了 404,用户会看到短时间不可访问。
防盗链和鉴权配置,也可能制造穿透
带 token 的资源很容易把 CDN缓存打碎。比如视频切片:
/vod/abc/001.ts?token=a1&expires=1717000001
/vod/abc/001.ts?token=b2&expires=1717000002
内容一样,token不同。如果 CDN按完整 URL 缓存,每个用户每个时间点都是不同缓存。视频业务里这会非常致命,源站回源带宽会被放大很多倍。
更好的方式通常是把鉴权放在 CDN边缘完成,鉴权参数不参与缓存键,鉴权通过后命中同一份缓存。或者使用路径签名,把可缓存内容和鉴权逻辑分开设计。这里要和安全要求一起看,不能为了命中率直接把 token 忽略掉。
刷新缓存不是越猛越好
上线时很多人习惯全目录刷新,甚至刷新根路径。这个操作在低峰期问题不大,在高峰期就是主动制造穿透。目录刷新后,边缘节点缓存失效,用户请求重新回源拉资源。资源越热,回源越猛。
更稳的发布方式是文件名带 hash:
app.8f3a1c.js
style.91bd2.css
新版本发布新文件,旧文件继续留在 CDN缓存里。HTML引用新资源即可。这样不需要频繁刷新大批静态资源,也不会让旧用户访问突然全部回源。
如果必须刷新,控制节奏。先刷新 HTML,再预热核心 JS/CSS/图片,再放量。大文件尤其不要边刷新边等用户自然回源,下载包、补丁包、视频热点内容要提前预热。
源站日志和 CDN日志要对上时间线
排查这类问题,时间线很重要。只看事故后的配置截图,经常看不出来。要把这些事件对上:
CDN规则变更时间。
资源发布和刷新时间。
源站带宽、连接数、CPU、负载变化时间。
CDN命中率下降时间。
回源状态码变化时间。
业务活动开始时间。
如果命中率下降发生在规则变更后 2 分钟,基本就不用先怀疑预热。如果刷新目录后回源飙升,预热和刷新策略要一起看。如果源站 5xx 先出现,随后 CDN命中率下降,还要考虑 CDN回源失败后缓存无法更新,导致更多请求异常。
一个现场排查顺序
线上正在被打穿时,别一上来就改一堆缓存规则。规则改错会扩大影响。比较稳的处理节奏是:
先限住源站入口,确认 Nginx、SLB、防火墙、WAF 是否能加临时限速。对明显异常 UA、Referer、IP段先挡一批。DDoS流量要走高防,不要指望普通 CDN缓存规则解决。
接着看 CDN TOP MISS 和 BYPASS。把前 100 个 URL 拉出来,按路径、后缀、参数聚合。静态资源被参数拆碎,就临时加参数忽略或缓存键归一化;路径规则没命中,就调优先级;源站 Header 导致不缓存,就临时配置强制缓存,但只对确定安全的静态路径做。
然后处理热点过期。对热点资源延长 TTL,开启 stale 或回源合并。如果 CDN不支持,就在源站 Nginx做 proxy_cache_lock,或者应用侧给热点数据加互斥重建。
再做预热。注意这一步不是救火的第一动作,除非明确是大文件冷启动。预热要针对已经确认的热点 URL,不要把带 uid、timestamp 的 URL 原样提交进去,那是在浪费预热额度。
哪些资源适合强制缓存
强制缓存要谨慎,但不是不能用。适合强制缓存的资源通常满足几个条件:内容公开、不含用户私有信息、URL变更能代表内容变更、源站响应不依赖 Cookie。
| 路径示例 | 建议 TTL | 备注 |
|---|---|---|
| /static/js/* | 7天到30天 | 文件名带 hash 可以更长 |
| /static/css/* | 7天到30天 | 发布时避免覆盖同名文件 |
| /images/* | 1天到30天 | 头像类要看是否会同名覆盖 |
| /download/* | 7天以上 | 发布前预热,避免大文件回源 |
| /api/* | 默认不强制 | 除非明确是公开只读接口 |
同名覆盖是缓存事故高发点。比如 /static/app.js 永远叫这个名字,内容变了以后靠刷新 CDN。只要刷新没完成,用户就可能拿到旧版本;刷新太猛,又可能打穿源站。带 hash 的文件名能省掉很多麻烦。
CDN源站回源链路也要看
有些穿透看起来是缓存问题,实际是回源链路太差。边缘节点回源慢,连接占用时间变长,同样 QPS 下源站连接数更高。海外 CDN 回国内源站时,这个问题更明显,跨境链路丢包、延迟、抖动都会影响回源效率。
如果用户主要在海外,源站也尽量靠近 CDN节点和用户区域。北美用户就别让所有回源都跨太平洋回国内;日本用户访问量大,可以在日本放源站或中间源。BGP、CN2、GIA这些线路差异,在回源稳定性上是能体现出来的。
这也是为什么选云服务器时不能只看 CPU 和内存。带宽峰值、线路质量、防御能力、磁盘 IO、网络抖动都要算进去。CDN命中率正常时看不出来,命中率掉下来时,源站质量就是最后一道缓冲。
配置改完以后,别只看命中率回升
规则修正后,要看几个具体变化:同一 URL 的 Age 是否增长,X-Cache 是否从 MISS 变成 HIT,回源请求数是否下降,源站连接数是否释放,TOP MISS 是否从静态资源变成合理的动态接口。
如果命中率回升但源站还是高负载,可能还有动态接口被刷、数据库慢查询、日志写入阻塞、WAF规则耗 CPU。CDN只能解决该缓存的内容,不能替应用层背所有锅。
实际使用中,比较稳的配置状态通常是:静态资源长缓存,发布靠 hash;必要参数参与缓存键,无关参数忽略;404短缓存;热点资源有 stale 或回源合并;大文件发布前预热;源站带宽按异常回源预留;高风险业务前面接高防。配置不复杂,但每一项都要和真实 URL、真实 Header、真实访问行为对上。