Skip to content

缓存一致性

Cache Aside、更新顺序竞态、延迟双删、最终一致性工程方案。

Updated View as Markdown
For humans

缓存一致性

缓存与数据库双写的一致性问题:读写都走两套存储,顺序不对就出现脏数据。面试主线:先讲清楚为什么难,再讲 Cache Aside 和它的竞态,再给工程兜底方案。

为什么难

数据库和缓存是两个独立系统,没有分布式事务。任何“先 A 后 B”的操作都存在中间态:

  • 写库成功、写缓存失败 → 缓存是旧值
  • 删缓存成功、写库失败 → 下次读会 miss,重新读库,问题不大
  • 并发读写交叉 → 可能把旧值写回缓存

所以核心结论先行:做不到强一致,目标是最终一致,缩小不一致窗口

Cache Aside(旁路缓存)

读流程:先查缓存,命中返回;miss 则查库、写缓存、返回。

写流程:先更新数据库,再删除缓存(删而不是更新缓存)。删除比更新好在哪:

  • 更新缓存有写写竞态:两个并发写,后写库的先写缓存,缓存就是旧值
  • 删除缓存让下次读重建,天然规避乱序

删缓存的竞态

“先更新库再删缓存”在极端时序下仍会不一致:

读后写回与删缓存交错

概率很低(读要在写删之间完成),但确实存在。解法:

  • 延迟双删:删缓存后等一小段时间(主从同步延迟窗口,如 500ms)再删一次。第二次删除兜掉“旧值写回”
  • 版本号/时间戳:缓存 value 带版本,写回时版本比缓存旧就丢弃
  • 消息队列异步重删:删缓存失败(Redis 抖动)时重试

延迟双删要点

流程:更新库 → 删缓存 → 睡 N 毫秒 → 再删缓存。

  • 为什么睡:等主从同步和并发读把旧值写回的动作完成
  • N 取多少:主从同步延迟的上界,常规 300 到 500ms;删库要删两次的原因:第一次删可能被旧值写回覆盖
  • 局限:睡眠期间请求变慢;极端竞态下仍非绝对一致。属于工程折中

最终一致的可靠方案

方案 机制 特点
MQ 异步删 写库后发消息,消费端删缓存,失败重试 可靠,引入消息中间件
Canal 订阅 binlog 监听数据库 binlog,变更驱动删缓存 与业务解耦,不侵入代码
重试队列 删失败进队列定时重试 兜底手段

强一致场景的诚实结论:缓存本质是牺牲一致性换性能。读多写少、容忍秒级不一致的业务才适合上缓存;要求强一致的写操作直接写库绕过缓存。

面试追问

  1. 为什么写时删缓存而不是更新缓存? 更新有写写竞态,删除让读重建,天然规避乱序。删缓存也简单
  2. 先删缓存再更新库有什么问题? 删完缓存后、写库前,并发读会把旧值写回缓存,不一致窗口更大
  3. 延迟双删为什么删两次? 第一次删可能被并发读的旧值写回覆盖,等一个主从同步窗口再删一次兜底
  4. 缓存一致性能做到强一致吗? 不能,目标是最终一致。强一致就绕过缓存
  5. Canal 方案的优点? 监听 binlog 不侵入业务代码,删缓存失败可重试,可靠性高
Navigation

Type to search…

↑↓ navigate↵ selectEsc close