计算机科学两大难题之一:缓存失效与 CDN 瞬时刷新奥秘
2026-08-14 · DevCraft Studio
缓存失效为何被称计算机科学两大难题?大白话讲清 Cache-Aside、Read-Through、Write-Through、Write-Behind 四种策略,以及 CDN 全球秒级刷新与 Stale-While-Revalidate 的奥秘。
如果有人问你:计算机科学里最难的两件事是什么?很多人会脱口而出“写 bug”或者“赶 deadline”。但真正在圈子里被反复引用的答案,来自一位叫 Phil Karlton 的老工程师,他当年在网景(Netscape)干活时留下了一句名言:“计算机科学只有两大难题:缓存失效(cache invalidation)和命名(naming things)。” 这句话出现在无数技术博客、面试八股和架构书的开头,连 Martin Fowler 都专门写文章记录它的各种变体。
乍一听你可能觉得好笑:缓存不就是个“临时存一份数据”的小玩意儿吗?怎么就成世界级难题了?别急,今天我们就把这层窗户纸捅破,顺便聊聊它和 CDN 全球秒级刷新之间,藏着多少让人头秃的细节。
延伸阅读
更多相关攻略推荐:泛域名 SSL 证书自动化避坑:Acme.sh + DNS API 、BGP 是互联网的"高德地图":打开网页时,数据是这样找路的、【优化线路 02】BGP / AS4837 常规线路年付 10 美元、高级玩家玩出花:如何在 VPS 上广播自己的 IP 地址?BYOIP、Cloudflare 国内慢、还弹验证码?CDN 替代方案盘点与两台。
一、为什么“让旧数据下岗”这么难
缓存的本质,是用一份“副本”来替你跑腿,省得每次都去慢吞吞的数据库里翻。这就像你家楼下的便利店:你不用每次都跑到城郊的工厂(数据库)去拿牛奶,楼下小卖部(缓存)囤一份,随用随取,快得多。
但麻烦也就出在“副本”二字上。只要存在副本,就必然存在“什么时候这份副本过期了、该换新的了”的问题。难点在于:数据在工厂改了,你怎么保证小卖部那一瓶也同步更新?这背后是时机、粒度、一致性和性能的四重拉扯。
- 时机太早:你一分钟刷新一次,等于白缓存,白白浪费资源反复去数据库取。
- 时机太晚:你一小时刷新一次,用户可能看到一小时的旧新闻、旧价格,闹出笑话甚至资损。
- 粒度太粗:改一条订单就把这客户所有历史订单全清掉,纯属浪费。
- 粒度太细:每条数据单独管,系统复杂到没人敢动。
这可不是纸上谈兵。Meta 的工程博客曾经透露,他们为了把 TAO 缓存的一致性从“六个九”(99.9999%)提升到“十个九”(99.99999999%)花了好多年,专门搞了一套叫 Polaris 的可观测系统。看似只差几个小数点,但 TAO 每天处理超过一千万亿(10 的 15 次方)次请求,哪怕六个九,每天也会产生几十亿次不一致。 再加上 Uber 的峰值定价缓存如果几秒内没刷新,乘客看到的价格就乱套;Stripe 的支付缓存不一致,商户可能被重复扣款。每一次大故障复盘,几乎都绕不开一句灵魂拷问:“缓存失效做对了吗?”
还有一个凶险的失败模式值得认识,叫“缓存踩踏”(cache stampede,也叫惊群效应)。当一个超热门的缓存键过期,一大波请求同时杀到,它们全都发现 miss,于是集体冲向数据库。本来缓存是用来保护数据库的,结果数据库被自己人活活锤爆。常见的防御手段是“请求合并”(只让一个请求去回源重建,其余的排队等)以及下面要讲的 stale-while-revalidate 后台刷新。
二、四种读写策略:有的图快,有的图稳
既然失效这么难,工程师们发明了几套“套路”,核心区别在于:数据变了的时候,谁来负责同步缓存?下面这四种是你面试和实战都绕不开的。
1. Cache-Aside(旁路缓存):最常用的万金油
这是最经典、最灵活的模式。应用自己既管缓存又管数据库:读的时候先问缓存,没有再去数据库取,顺手写回缓存;写的时候先改数据库,再把缓存里那条删掉(注意是删除而不是更新,原因下面说)。
为什么是“删”而不是“更新”?想象两个写请求 A 和 B 同时改价格:如果写完就更新缓存,可能出现 A 改完数据库、B 改完数据库、B 更新了缓存、结果 A 的缓存把 B 覆盖了——缓存里停着旧价格。改成“写后删缓存”,让下一次读去重新加载,就绕开了这个并发坑。代价是:第一次请求必然 miss,而且删缓存到下次读之间有个极短的“不一致窗口”。
2. Read-Through(读穿透):把锅甩给缓存层
应用只跟缓存说话,数据库的事全交给缓存中间件去办。缓存没命中,它自己跑去数据库取,再返回给你并记下来。好处是业务代码清爽,不用自己写“查库 + 写回”那一套;坏处是缓存层得懂你的数据库,灵活性差一些,而且对不常读的数据也往缓存里塞,有点浪费。
3. Write-Through(写穿透):强一致性的代价
写请求先写缓存,缓存同步地把数据写进数据库(同步、同一事务)。好处是缓存永远和数据库一致,读逻辑极简;坏处是每次写都要写两遍,写延迟高,写密集的场景会吃不消。库存、支付金额这类“错一毛钱都不行”的数据,才值得上这个模式。
4. Write-Behind(写回):快到飞起,但可能丢数据
应用只写缓存,立刻返回成功;缓存把写操作丢进队列,后台慢慢(批量、延迟)刷回数据库。写入性能极高,对数据库冲击小,适合“写爆发”场景。但代价刺眼:如果缓存服务在刷盘前崩了,那批数据就永久丢失了。 所以浏览历史、打点日志这类“丢了不心疼”的数据才用它,用户核心数据碰都不敢碰。
一句话总结:Cache-Aside 胜在灵活通用,Write-Through 胜在强一致,Write-Behind 胜在极速但冒险,Read-Through 胜在代码干净。选哪个,取决于你“能容忍哪种失败”,而不是哪个写起来最爽。
三、CDN 节点上的“全球秒级刷新”黑魔法
把视角从单机内存缓存拉到全球 CDN,难度指数级上升。像 Cloudflare 这种厂商,边缘节点遍布全球 300 多个。你更新了一个 CSS 文件,怎么让东京、法兰克福、圣保罗的用户都在一秒内看到新版本?
答案是 Purge(清除)API。本质上,你发一个 HTTP 请求给厂商,告诉他们“这几个 URL 的缓存作废了”。Cloudflare 把这个叫 Instant Purge,他们公布的 P50 延迟大概在 250 毫秒(目标压到 200 毫秒以内)。也就是说,一次 API 调用,全球边缘节点的对应副本就一起被标记为失效,下次访问直接回源取新内容。
这里有个关键设计:缓存键(cache key)怎么定。 你 Purge 时填的 URL,必须和缓存写入时用的键一模一样,否则就会出现“我明明清了,用户却还看到旧页面”的灵异事件。所以很多工程团队会把“URL 计算公式”单独抽成一个模块,读写两侧共用,从根上消灭键不一致。Cloudflare 还支持按 Cache-Tag(缓存标签)、按前缀(prefix)、按主机名(hostname)批量清除——比如你改了全站某个组件,直接 purge 掉带某个 tag 的一组资源,比一个个 URL 清高效得多。
当然厂商也有“节流阀”。Cloudflare 免费版令牌桶是 25 个请求、每 12 秒补充一次,付费版更宽松。清得太狠会把回源流量瞬间打爆,既烧钱又可能把源站打挂;清得太轻,用户又卡在旧页面。所以老手的策略是:能按 tag / 前缀精准清,就别“Purge Everything”一键清空。
四、Stale-While-Revalidate:先给你旧的,后台偷偷换新
有没有一种办法,既不让用户等,又能保证最终拿到新内容?有,叫 Stale-While-Revalidate(后台异步刷新)。CloudFront、Cloudflare、Fastly 都支持。看这个响应头例子:
Cache-Control: max-age=3600, stale-while-revalidate=600
意思是:这份内容先缓存一小时(max-age=3600)。一小时后它“过期”了,但别急着让用户干等——边缘节点继续把旧内容返回给用户,同时在后台悄悄去源站重新取一份新的,最多宽限 600 秒(10 分钟)。等新内容到手,后续请求就自动换成新的。用户全程零等待,体验丝滑;只有真正关心“必须此刻绝对最新”的内容(比如股价、库存)才不适合这么玩。
再叠加一个 stale-if-error:万一源站挂了,边缘节点还能继续吐旧内容顶一阵子(比如 24 小时),用户甚至感知不到后端宕机。这套组合拳,就是“速度与新鲜度”的完美折中。
五、给你的几条实在建议
- 别盲目崇拜缓存,先想清楚“这份数据能忍受多旧”,再定 TTL 和刷新策略。
- 写后优先“删缓存”而非“更新缓存”,避开并发覆盖坑。
- 一致性要求高就用 Write-Through,写爆发又不怕丢就用 Write-Behind,绝大多数场景 Cache-Aside 足够。
- CDN 刷新用 Purge API + 精准 tag / prefix,别动不动全站清空。
- 对延迟敏感又想保新鲜,上 Stale-While-Revalidate + stale-if-error 组合。
缓存失效之所以被称为“两大难题”,不是因为它需要多高深的算法,而是因为它本质上是一个协调问题:当一份数据在某处变了,怎么保证天下所有副本都“刚好在对的时间”被更新或删除。想通这一点,你就超过了绝大多数只会背八股的人。
常见问题 FAQ
问:什么是缓存失效?答:缓存失效( purge )是主动清除 CDN/反向代理中旧内容,让下次请求回源取最新。改了文章/样式后不失效,访客会看到旧版。理解 TTL 与主动 purge 的关系,是做好缓存的关键。
问:什么时候需要手动清除?答:更新了 CSS/JS、改了文章正文或换了图片,但浏览器/CDN 仍显示旧版时,应手动 purge 对应 URL 或整站。配合版本号(如 style.css?v=2)可避免强清,提升命中率与性能。
问:CDN 清缓存慢怎么办?答:先确认清的是边缘节点还是全节点,部分 CDN 全球生效需几分钟。可临时调低 TTL 或开启开发者模式绕过缓存排障;并用 curl -I 看 Cache-Control/age 头验证是否生效,定位是 CDN 还是源站问题。