双写、延迟双删、订阅 binlog,什么场景下各自更稳?
缓存一致性最怕一上来就争论方案名字。把业务能容忍多久旧值、数据库是不是唯一事实来源、失败后怎么补偿、热点 key 回源有多贵这些前提摆清,再看双写、延迟双删和 binlog/CDC,结论通常会直接收敛。
做缓存一致性设计时,团队最后几乎总会绕到三个方案上:
- 双写数据库和缓存
- 更新数据库后做延迟双删
- 订阅 binlog / CDC,再异步删缓存或刷新缓存
这三个方案几乎每个团队都讨论过,但争论也经常绕成一句没什么帮助的话:
- 这个方案更优雅
- 那个方案更实时
- 这个业界用得更多
真正到线上出问题时,这种抽象比较通常没什么用。
因为缓存一致性真正难的,不是概念上知道这三种方案,而是:
- 你的业务能容忍多长时间的旧值
- 数据库是不是唯一事实来源
- 写入失败和删除失败怎么补偿
- 热点 key 删除后,数据库能不能扛住回源
- 并发写入和乱序写入下,会不会把旧值重新种回去
也就是说,双写、延迟双删、binlog 方案之间,不存在脱离场景的“谁绝对更好”,只有“谁在什么条件下更稳”。
我更关心的不是这三个方案哪个名字更高级,而是线上出事时谁更容易补、谁更容易把旧值重新种回去。判断时别先比术语,更该比这几件事:事实来源站在哪边、旧值窗口有多长、失败后有没有补偿、并发乱序会不会把缓存写回旧值、热点回源能不能扛住。
如果你已经看过《缓存一致性问题为什么总在删缓存、改数据库之后出事?》,可以把这篇当成下一步:前一篇把问题链条摊开,这一篇专门拿来做方案取舍。
你现在更卡在哪一步
| 你现在的目标 | 更值得先看什么 | 为什么 |
|---|---|---|
| 想比较双写、延迟双删、binlog / CDC 哪种更稳 | 直接往下看这篇 | 重点就是把三种常见做法放到同一套取舍里比较 |
| 你还没把一致性里的时序、旧值回填和回源放大理顺 | 先看《缓存一致性问题为什么总在删缓存、改数据库之后出事?》 | 先把问题链条看清,再谈选型才不会飘 |
| 你在处理的是穿透、击穿、雪崩或热 key | 先去看热点和缓存故障类型相关文章 | 这些问题和一致性选型不是一回事,混着看容易走偏 |
| 你要的是某个方案的代码实现细节 | 把这篇当成选型前的判断清单 | 它更偏方案边界和取舍,不替代具体实施文档 |
把比较口径定清楚
如果你已经在双写、延迟双删和 binlog / CDC 之间摇摆,就直接按本文的比较框架往下看。
这里不想再重复“一致性为什么会出事”,而是把几个真正决定方案优先级的条件摊开:数据库是不是唯一事实来源、业务能容忍多长旧值窗口、失败后怎么补偿、热点回源代价有多高,以及并发乱序是不是常态。
判断顺序可以这样看
如果你现在正在选一致性方案,第一轮别急着比术语,先把数据库是不是唯一事实来源、业务能容忍多短的旧值窗口、写入失败怎么补偿、热点 key 回源代价有多高、以及并发乱序写会不会发生写清。因为双写、延迟双删、binlog / CDC 之间没有脱离场景的优劣,把这些边界写清,后面比较才不会变成空谈。
- 先定数据库是不是唯一事实来源。
- 再定业务能容忍多短的旧值窗口,而不是先选方案。
- 再看写路径分散程度、补偿能力和热点 key 的回源代价。
- 如果你更怕两个真相源一起写乱,就不要轻易走双写。
- 如果你已经知道自己只能接受最终一致,再在延迟双删和 binlog / CDC 之间比较治理成本和失败模型。
一、别急着讨论方案,先定一个前提:数据库是不是唯一事实来源
这是比较三种方案前最重要的一步。
对大多数业务系统来说,更稳的默认前提通常是:
- 数据库是唯一事实来源
- 缓存是派生副本
- 缓存的任务是加速读,不是跟数据库一起并列做两份真相
一旦这个前提成立,很多方案的优先级就会很不一样。
1. 如果数据库是唯一事实来源
更稳的目标通常是:
- 先保证数据库写入正确
- 再让缓存尽快失效或收敛
- 失败时有补偿链路
- 接受短时间最终一致,而不是追求假想中的绝对同步
2. 如果你试图让缓存也变成事实来源之一
那你马上就会遇到:
- 双写顺序
- 两边失败不一致
- 并发覆盖和乱序写回
- 回滚与补偿复杂度爆炸
所以大多数普通业务里,真正稳的方案并不是“把缓存写得像数据库一样可靠”,而是:
- 承认数据库是主
- 让缓存做副本
- 让缓存尽快失效或异步收敛
这也是为什么下面三种方案里,真正值得优先比较的不是“是否实时”,而是“是否更符合数据库为事实来源的前提”。
二、先建立一个比较框架:别先问谁更高级,先问谁在哪些地方更容易翻车
比较这三类方案时,我更建议至少从下面五个维度看:
- 旧值窗口有多长
- 失败后有没有自然补偿路径
- 并发写入和乱序写入时,会不会把旧值写回去
- 对热点 key 和回源流量有没有额外副作用
- 实现复杂度和维护成本有多高
这五个维度一摆出来,很多原本抽象的争论会马上具体很多。
三、再看双写:看起来最实时,但也最容易把两个真相源一起写乱
双写通常指的是:
- 写数据库
- 同步写缓存
有的团队还会进一步细分:
- 先写数据库再写缓存
- 或先写缓存再写数据库
但无论顺序怎么排,双写的根问题都一样:
你开始同时维护两个状态源,而且还希望它们时刻一致。
双写的优势
从直觉上看,双写最大的好处是:
- 缓存更新及时
- 读请求更少走 miss
- 对热点 key 看起来比较友好
- 不像删缓存那样有显式空窗期
双写真正的高风险点
1. 一个成功,一个失败
例如:
- 数据库写成功,缓存写失败
- 缓存写成功,数据库回滚或失败
这时你立刻进入补偿地狱。
2. 并发写入乱序覆盖
这是双写最麻烦的一点。
比如:
- 请求 A 把值改成 120
- 请求 B 紧接着把值改成 130
- B 的缓存写先成功
- A 的缓存写因为网络或重试晚到一步,又把缓存写回 120
最后就会出现:
- 数据库是 130
- 缓存却变回 120
3. 缓存写入逻辑越来越像第二套事务系统
为了修上面这些问题,你很快就会开始加:
- 版本号
- 条件更新
- 幂等控制
- 重试和补偿
- 乱序覆盖保护
这时缓存已经不再只是缓存,而像一套简化版数据库同步系统。
双写更适合什么场景
只有在下面这些前提比较明确时,双写才相对更稳:
- 你非常在意旧值窗口尽量短
- 写入并发不算夸张,或者能通过版本号防乱序
- 缓存写入支持条件更新或至少带版本校验
- 团队能长期维护这套写入一致性复杂度
如果这些前提做不到,双写通常不是“更先进”,而是“更脆弱”。
四、再看延迟双删:它不追求绝对同步,而是用两次删除覆盖并发窗口
延迟双删的典型思路是:
- 更新数据库
- 删除缓存
- 过一小段时间再删一次缓存
也有团队会写成“先删缓存、再写数据库、延迟再删”,但对大多数数据库为事实来源的业务来说,更常见的稳妥主线还是:
- 先更新数据库
- 尽量在事务提交后删缓存
- 再做一次延迟补删
延迟双删想解决什么
它真正要解决的是:
- 第一次删缓存之后
- 可能有并发读请求去数据库读到旧值
- 又把旧值回填回缓存
于是隔一段时间再删一次,试图把这部分并发窗口盖住。
延迟双删的优势
- 保持数据库为事实来源
- 实现难度比双写低
- 对大多数可容忍短暂旧值的业务更友好
- 不强迫缓存承担第二真相源角色
延迟双删的局限
1. 它本质上是概率补丁,不是确定性方案
因为你永远无法保证:
- 第二次删除恰好踩中所有竞争窗口
- 事务时间、网络延迟、下游耗时都刚好在你的估计范围内
2. 延迟多久没有标准答案
- 太短,覆盖不了真实窗口
- 太长,旧值窗口又太长
3. 删除失败仍然需要补偿
如果第二次删也失败,而且没有重试、告警和补偿链路,那还是会留下旧值。
4. 热点 key 删除后可能带来回源抖动
尤其是热点对象,一删缓存就会出现:
- miss 增多
- 回源数据库抬高
- 连接池和接口 RT 被一起放大
所以延迟双删从来不是只看一致性,还要看数据库和回源承压能力。
延迟双删更适合什么场景
更适合:
- 数据库明确是事实来源
- 业务能容忍短时间旧值
- 读多写少
- 能接受最终一致
- 已经有基本的删除重试和补偿能力
对这类场景来说,延迟双删通常比双写更稳,也更容易维护。
五、再看 binlog / CDC:它更像“数据库提交后驱动缓存收敛”的异步一致性链路
binlog / CDC 方案的核心思路是:
- 应用只负责把数据库写对
- 数据库提交后,由 binlog 或 CDC 事件驱动缓存删除或刷新
它的最大优点在于:
- 删除动作天然站在“数据库已经成功提交”之后
- 不需要业务代码在事务里自己判断那么多时序细节
binlog 方案的优势
1. 更符合数据库为事实来源
因为它的触发点是:
- 数据库提交成功
- 数据变化成为事实之后
- 再驱动缓存收敛
2. 对业务代码侵入小
业务侧不一定需要在每条写路径里手动维护复杂删除逻辑。
3. 更适合多写路径、多服务、多语言系统
如果同一份数据可能被:
- Java 服务改
- Python 脚本改
- 后台运营系统改
- 批任务改
那把缓存失效只写在某一层业务代码里,天然容易漏。binlog 方案在这里会更统一。
binlog 方案的局限
1. 它不是强一致,而是更可控的异步最终一致
因为从数据库提交到缓存失效,仍然存在传播延迟。
2. 消费链路本身也会失败
例如:
- 消费积压
- 消息延迟
- 订阅程序挂掉
- 映射规则写错
- 下游 Redis 删除失败
如果这条链没人监控、没人补偿,问题只是从应用代码里搬到了 CDC 流水线里。
3. 变更映射复杂时,维护成本不低
最难的往往不是“收到 binlog”,而是:
- 这条表变更对应哪些缓存 key
- 需要删哪些聚合视图
- 是否跨表、跨对象、跨服务
binlog 方案更适合什么场景
更适合:
- 数据库是明确主事实源
- 写路径分散,不想每条业务代码都维护失效逻辑
- 可以接受短时间最终一致
- 有能力建设事件消费、监控、重试和补偿体系
如果团队基础设施比较成熟,binlog / CDC 通常会比在业务代码里到处散落删除逻辑更稳。
六、把三种方案放到同一张表里理解:它们各自“稳”的条件不一样
如果用一句话分别概括:
- 双写:更追求实时,但最怕并发乱序和双边失败
- 延迟双删:更偏工程折中,前提是能接受短暂旧值和热点回源
- binlog / CDC:更偏平台化收敛,前提是你能维护好异步消费链路
换成更实战的判断:
1. 如果你的业务最怕旧值窗口,但写并发不高、可带版本控制
双写才有讨论空间。
2. 如果你的业务默认接受最终一致,数据库就是主,写路径不算特别分散
延迟双删通常是更稳的默认起点。
3. 如果你的写路径很多、改数据的人很多、服务很多,希望统一收口
binlog / CDC 往往更适合做中长期方案。
七、别漏掉一个关键维度:热点 key 和回源代价,会改变三种方案的“稳不稳”
缓存一致性方案比较时,很多文章只比时序,不比回源副作用,这是不够的。
1. 双写对热点 key 的好处
它减少了主动删缓存造成的 miss 空窗期。
2. 延迟双删和 binlog 删除,对热点 key 更容易引发回源
因为它们的默认动作通常是:
- 删缓存
- 让后续读请求再回源重建
如果这个 key 很热:
- 一次删除就可能带来大批 miss
- 数据库和连接池很容易抖
所以你在比较方案时,一定要同时问:
这个 key 热不热?删掉后数据库能不能扛住回源?
如果这个问题不先回答,很多一致性方案在纸面上很漂亮,线上却很脆。
八、一个更实用的选择顺序:先定业务容忍度,再选方案,不要反过来
如果以后再碰到“到底双写、延迟双删还是 binlog 更稳”,我更建议按这个顺序选。
第 1 步:先定业务容忍度
先回答:
- 这个字段能不能接受短暂旧值
- 能接受多长
- 是展示型字段,还是交易决策字段
第 2 步:再定事实来源
通常更稳妥的默认前提是:
- 数据库是唯一事实来源
第 3 步:再看写路径分散程度
- 写路径集中,还是很多系统都在改
- 是否适合在业务代码里收口
- 是否更适合走 CDC 统一收口
第 4 步:再看热点和回源代价
- key 热不热
- 删除后数据库扛不扛得住
- 是否需要互斥回源、异步刷新或其他保护手段
第 5 步:最后才选实现方案
通常会收敛成:
- 低并发、短旧值敏感、可带版本控制:才考虑双写
- 一般业务、可最终一致:优先延迟双删或提交后删缓存
- 写路径多、需要统一治理:考虑 binlog / CDC
九、一个典型案例:为什么同样是价格字段,有团队选延迟双删,有团队选 binlog
假设两个团队都在处理商品价格缓存一致性。
场景 A:单服务维护、写路径集中
特点:
- 只有一个核心 Java 服务写价格
- 更新频率中等
- 业务能容忍几百毫秒旧值
- 团队暂时没有成熟 CDC 基础设施
这类场景下,更合适的做法通常是:
- 数据库提交后删缓存
- 再做延迟补删
- 补齐删除失败重试和告警
场景 B:价格会被多个系统更新
特点:
- 运营后台会改
- 批量调价脚本会改
- 促销系统会改
- 不同语言服务都可能改
这时如果还把失效逻辑散在每条业务代码里,非常容易漏。
更合适的方向通常就是:
- 让数据库写入成为唯一入口事实
- 用 binlog / CDC 驱动价格缓存失效或刷新
这个例子很能说明:不是某个方案天然更高级,而是它更适合某种系统边界。
十、关键误判:这类方案比较最容易在哪些地方走偏
误判 1:双写更实时,所以一定更好
错。
它只是旧值窗口更短,但并发乱序和失败补偿复杂度通常更高。
误判 2:延迟双删是经典方案,所以可以无脑套
错。
如果热点 key 很热、数据库回源代价很高,删缓存本身就可能成为放大器。
误判 3:binlog 方案最优雅,所以一定最稳
也错。
如果 CDC 消费链路没有监控、重试、积压治理,它同样会悄悄失效。
误判 4:只比较一致性,不比较回源代价
很多线上事故不是旧值先出事,而是删缓存后的回源先把数据库打疼。
误判 5:先选方案,再去硬套业务容忍度
顺序反了。
更可靠的做法永远是:先定容忍度和边界,再选方案。
十一、FAQ:双写、延迟双删、binlog 方案里最常被问到的几个问题
1. 默认应该优先考虑哪个方案?
对大多数“数据库是事实来源、业务可接受最终一致”的普通业务,默认更推荐:
- 数据库提交后删缓存
- 必要时再加延迟补删或异步补偿
2. 什么时候双写才值得考虑?
当你非常在意旧值窗口,而且能处理版本控制、幂等、乱序覆盖和双边失败时,双写才有讨论价值。
3. binlog / CDC 是不是比延迟双删更高级?
不该这么比。
它更适合写路径分散、需要统一收口的系统,但前提是你有能力维护这条异步消费链路。
4. 延迟双删是不是银弹?
不是。
它只是用第二次删除覆盖一部分并发窗口,解决不了所有乱序、失败和热点回源问题。
十二、把方案比较落回现场
下面这些文章分别补不同问题,不是把双写、延迟双删、binlog / CDC 再换个标题重讲一遍。
如果你还在补缓存侧证据
- 缓存一致性问题为什么总在删缓存、改数据库之后出事?
- 缓存 TTL 设计不当,为什么会把高峰流量集中打回数据库?
- 缓存层出问题后,为什么应用和数据库会一起变慢的等待型链路?
- 缓存回源流量突然放大时,到底先查业务热点、命中率还是数据库压力?
如果影响已经扩到别的链路
如果你准备按现场一步步拆
- 先看《缓存一致性问题为什么总在删缓存、改数据库之后出事?》,把删除失败、旧值回填和回源放大这条主线先吃透。
- 回到这篇,再把双写、延迟双删、binlog / CDC 放进同一套比较框架里看代价和收益。
- 如果你大概率会走删缓存路线,接着看 缓存 TTL 设计不当,为什么会把高峰流量集中打回数据库? 和 缓存回源流量突然放大时,到底先查业务热点、命中率还是数据库压力?
- 如果回源已经把数据库和接口一起拖慢,再去看 缓存层出问题后,为什么应用和数据库会一起变慢的等待型链路?
- 如果你已经看到事务、连接池和接口 RT 一起被放大,再把数据库等待链相关文章连起来看。
十三、最后总结:三种方案没有绝对优劣,只有谁更符合你的事实来源和失败模型
缓存一致性方案比较里,最容易把人带偏的地方就是:
- 总想找一个放之四海而皆准的“最优方案”
- 却没有先定清楚业务容忍度、事实来源和失败补偿边界
真正更值得遵守的主线应该是:
先承认数据库是不是唯一事实来源,再看业务能容忍多短的旧值窗口、写路径有多分散、热点 key 的回源代价有多高,最后再选双写、延迟双删还是 binlog / CDC。
只要这条顺序不乱,很多原本停留在术语层的争论,最后都会收敛成一个更具体、更可落地的工程决策问题。