“为什么我们买了最贵的CDN套餐,源站数据库的CPU还是在报警?”
这是我职业生涯早期最常问自己的一个问题。那时候我天真地以为,CDN(内容分发网络)就是一个“傻瓜式”开关,只要把域名的CNAME指过去,网站就能自动起飞。
直到2020年的一次大促活动,我们的服务器在流量峰值时崩了。排查后发现,虽然CDN显示流量很大,但**回源率(请求穿透CDN直达源站的比例)**竟然高达35%。这意味着,CDN基本没拦住多少请求,钱白花了,服务器还挂了。
那次事故后的复盘会议,我喝了整整三杯冰美式才冷静下来。从那以后,我养成了一个习惯:每接手一个新系统,第一件事就是体检它的缓存策略。
今天我想分享3个真实的“血泪”案例,希望能帮你避开那些看似不起眼、实则致命的CDN配置大坑。
忽略参数的代价:这行代码让缓存彻底失效
观点:对于静态资源,如果不处理URL后缀参数,CDN会把它当作“新文件”反复回源。
真实案例:
那是2021年的一个电商项目。运营团队为了追踪各个渠道的推广效果,给首页所有的Banner图片链接都加上了追踪参数,比如 banner.jpg?channel=tiktok 或者 banner.jpg?utm_source=email。
发生了什么?
本来这张 banner.jpg 只需要被CDN缓存一次,之后几十万用户访问都应该直接从CDN边缘节点读取。
但因为CDN默认配置是**“带参数缓存”**,在CDN眼里,banner.jpg?v=1 和 banner.jpg?v=2 是完全不同的两个文件。
结果就是,每次有一个新的参数组合(甚至只是时间戳不同),CDN就会认为自己“没有这个文件”,转头向源站服务器请求。那天下午,我们的源站带宽瞬间打满,因为所有所谓的“缓存”都变成了透传。
避坑方案: 针对图片、CSS、JS等静态资源,必须在CDN后台开启**“忽略参数缓存”**(或称“去问号缓存”)。
这就好比你去奶茶店点单,不管你说“我要一杯珍珠奶茶,我是小红推荐来的”还是“我要一杯珍珠奶茶,我是小明推荐来的”,店员(CDN)都只应该给你那杯标准的珍珠奶茶,而不是因为推荐人不同就重新去后厨(源站)现做。
配置示例:
大多数云厂商控制台都有图形化开关,但如果你用Nginx做前置代理,逻辑大概是这样的:
# 这是一个反面教材,不要让Vary Header或者参数影响静态文件
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
# 确保你的CDN厂商配置了"Ignore Query String"
# 或者在源站强行设置缓存时间
expires 30d;
add_header Cache-Control "public, no-transform";
}
隐形的杀手:Vary Header 导致的缓存碎片
观点:HTTP响应头里的 Vary 字段如果不加控制,会把一份缓存分裂成成千上万份,导致命中率雪崩。
真实案例:
这是一个新闻资讯类的API优化案例。我们的架构师为了适配移动端和PC端,在后端代码里加了一个逻辑:根据请求头里的 User-Agent 返回不同的JSON结构,并且顺手加上了 Vary: User-Agent 响应头。
发生了什么?
Vary 告诉CDN:“虽然URL一样,但如果User-Agent不一样,你要存不同的副本哦。”
问题在于,现代浏览器的 User-Agent 极其复杂,包含版本号、系统版本等信息(例如 Chrome 90.0.1 和 Chrome 90.0.2 就是不同的)。
结果,CDN节点上本来应该只有一份的 news_list.json,变成了几千份不同的缓存副本。用户的请求极难命中已有的副本(因为版本号稍有不同),缓存命中率直接跌到了5%以下。
避坑方案:
千万不要轻易对公共资源设置 Vary: User-Agent。
如果必须根据设备类型返回不同内容,建议:
- 拆分URL:使用
m.example.com和www.example.com。 - CDN层标准化:在CDN边缘节点通过脚本(如EdgeScript)把复杂的User-Agent归一化为简单的
mobile或desktop两个值,然后再回源。
经验之谈:我通常建议开发团队,除非万不得已,否则直接在CDN配置中**剥离(Strip)**掉源站返回的
Vary头,或者仅保留Vary: Accept-Encoding(用于区分Gzip/Brotli压缩)。
被忽视的404:恶意扫描击穿防线
观点:错误响应(404/500)如果不缓存,也会成为攻击者的后门。
真实案例:
这个案例发生在一个周五的傍晚(似乎故障总选在周五)。监控报警显示数据库连接数激增,但正常业务流量并没有涨。
排查日志发现,有大量的爬虫在扫描我们网站并不存在的路径,比如 /admin/login.php,/backup.zip 等。
发生了什么? 因为这些文件确实不存在,源站返回了 404 Not Found。 但是!我们默认的CDN策略是不缓存404状态码的。 这意味着,爬虫每秒发起1000次扫描,这就实打实变成了1000次对数据库或文件系统的IO查询。这就是典型的**“缓存穿透”**。
避坑方案: 对错误状态码设置短时间的缓存。
我们当时的紧急处理方案是:在CDN上配置,针对 404 状态码,缓存 60秒 到 300秒。 这样,当爬虫第一次请求一个不存在的文件时,源站返回404,CDN把它记下来。接下来的几分钟内,无论爬虫怎么扫,CDN都会直接挡回去说“没有”,源站就在后面安稳地休息。
落地配置建议:
| 状态码 | 建议缓存时间 | 原因 |
|---|---|---|
| 200 (成功) | 视业务而定 (1小时-30天) | 正常业务数据 |
| 301/302 (重定向) | 10分钟 - 24小时 | 避免反复重定向请求源站 |
| 404 (未找到) | 1分钟 - 5分钟 | 防止恶意扫描穿透 |
| 500/502 (错误) | 0秒 (不缓存) | 错误通常需要立即恢复,不要缓存 |
结语
CDN 绝不是“买了就快”的银弹。它更像是一辆高性能跑车,如果不调教好悬挂和轮胎(配置策略),跑起来可能比拖拉机还颠。
回顾这三个案例,其实核心逻辑就一条:尽可能把一切算力消耗留在边缘,让源站只处理必须处理的动态逻辑。
这几年,每当我接手优化任务,这套组合拳屡试不爽:
- 查参数:静态资源是否开启了“忽略参数”?
- 看头信息:Response Header 里有没有奇怪的
Vary或Cache-Control: private? - 防穿透:404和301状态码是否配置了缓存时间?
最后,留一个问题给各位同行: 你在排查网络性能问题时,遇到过最离谱的配置是什么?是配错了HTTPS证书,还是缓存时间设置成了0?欢迎在评论区聊聊你的“踩坑”经历。
给新手的3个落地行动步骤:
- 打开浏览器开发者工具 (F12):选中 Network 标签,刷新你的网站资源,查看 Response Headers 中的
X-Cache字段。如果是MISS或TCP_MISS,说明没命中缓存,需要警惕了。 - 检查CDN控制台:重点查看“缓存过期时间”配置,确保图片、CSS等静态文件至少缓存 7天以上,并开启“忽略URL参数”。
- 灰度测试:在修改任何缓存策略前,先在一个非核心的域名或路径上测试,观察10分钟日志,确认没有异常(如页面排版错乱)后再全量发布。