★ 如何保证缓存与数据库一致性?Cache Aside 与延迟双删怎么用?

结论先行:业界默认方案是 Cache Aside(旁路缓存):读 miss 时回源数据库并写缓存;写操作先更新数据库,再删除缓存,而不是更新缓存。由于并发窗口无法用删除操作完全消除,工程上普遍接受"缓存带 TTL 的最终一致",再用延迟双删、消息队列重试等手段压缩不一致窗口。

一、为什么是"更新数据库 + 删除缓存"

  • 先删缓存再更新数据库:删除后到 DB 更新完成前,其他线程读到旧值写回缓存,产生长期脏数据;
  • 更新缓存而非删除缓存:写频繁时缓存被无意义刷新,且并发写顺序不可控,最终缓存值可能与 DB 相反;
  • 删除缓存的正确姿势:先更新 DB,再删缓存;即使删缓存失败,缓存命中旧值的时间也受 TTL 约束;
  • 缓存一定要设置过期时间:TTL 是最终一致性的最后兜底,防止删除失败造成永久不一致;
  • 读路径的单飞:读 miss 回源时对同一 key 做互斥或单飞,避免热点 key 失效瞬间多个请求同时查库。

二、双删与延迟双删

  • 双删:更新 DB 后删除缓存,等一小段时间(如 500ms)再删一次,覆盖"旧值读回"的竞态窗口;
  • 延迟双删只是缩小窗口:极端并发下第二次删除前仍可能写入旧值,因此不能替代 TTL 兜底;
  • 删除失败的补偿:把删除操作发到消息队列异步重试,或用 Canal 订阅 binlog 触发删除,保证删除最终成功;
  • 双删间隔取值:要大于一次缓存重建的最坏耗时,否则第二次删除可能发生在旧值写回之前,常见经验值 300~1000ms。

三、一致性级别选择

需求方案
强一致(金额等)直接读写数据库或对同一 key 串行化访问,缓存仅作旁路加速
最终一致(多数业务)Cache Aside + 延迟双删 + TTL + 删除补偿
一致性要求低Cache Aside + TTL 即可,删缓存失败可容忍短暂旧值

四、删除失败补偿的命令示例

# 删除失败时把任务投递给 MQ,由消费方重试删除缓存(示意)
PUBLISH cache:del order:1001

提示:补偿任务消费时要再次尝试删除缓存并记录日志,连续删除失败应告警人工介入。

常见追问 / 记忆点

  • 追问:为什么不用更新缓存而是删除缓存?答:更新缓存成本高且并发写序不可控,删除让下次读时按需重建,天然收敛到新值。
  • 追问:延迟双删能保证强一致吗?答:不能,只能显著缩小窗口,强一致必须读写串行化或放弃缓存。
  • 追问:删除失败怎么办?答:异步重试队列 + 订阅 binlog 补偿删除,配合短 TTL 兜底。
  • 记忆点:先库后删、延迟再删、TTL 兜底、异步补偿,四件套说完即完整。
笔记加载中…