Redis 缓存与数据库双写一致性问题怎么解决

在分布式系统中,Redis 通常被用作缓存层来减轻数据库压力、提升读取性能。但一旦引入缓存,就不可避免地面临一个经典难题:当数据同时存在于数据库和 Redis 中时,如何保证两者的数据一致性? 这也是面试中高频出现的考点,本文将系统梳理各种方案及其优缺点。

为什么会出现双写不一致

先看一个典型的更新流程:

  1. 更新数据库中的值
  2. 删除 Redis 中的缓存

看起来没问题,但在并发场景下可能出问题。假设有两个线程 A 和 B:

  • 线程 A 更新数据库为值 1
  • 线程 B 更新数据库为值 2
  • 线程 B 删除缓存
  • 线程 A 删除缓存

由于网络延迟或调度顺序,最终缓存被删除后,下一次读取会把数据库中的值 2 加载进缓存,这看起来没问题。但如果是下面这种顺序:

  • 线程 A 查询缓存,未命中
  • 线程 A 查询数据库,得到旧值 1
  • 线程 B 更新数据库为值 2
  • 线程 B 删除缓存
  • 线程 A 将旧值 1 写入缓存

此时缓存中是旧值 1,数据库中是值 2,出现了长期不一致,直到缓存过期才会恢复。

常见解决方案

1. 先更新数据库,再删除缓存(Cache Aside)

这是最常用的方案,也叫旁路缓存模式。读请求先读缓存,未命中则读数据库并回写缓存;写请求先更新数据库,再删除缓存。

优点:实现简单,大多数场景下能保证最终一致。

缺点:极端并发下仍可能不一致(如上文所述)。但这种情况概率极低,因为要求读操作在写操作之前开始,且写操作在读完数据库后、写缓存前完成删除,时间窗口很小。

2. 延迟双删

在更新数据库前后各删除一次缓存,中间加一段延迟:

删除缓存
更新数据库
延迟 N 毫秒
再次删除缓存

延迟时间一般设置为读业务逻辑耗时加上几百毫秒,目的是把可能在第一次删除后、更新数据库前读到的旧数据再次清掉。

优点:能覆盖大部分并发不一致场景。

缺点:延迟时间难以精确设定;第二次删除失败则仍然不一致。

3. 先删除缓存,再更新数据库

这种顺序在并发下更容易出问题:

  • 线程 A 删除缓存
  • 线程 B 读缓存未命中,读数据库旧值,写入缓存
  • 线程 A 更新数据库为新值

结果缓存中是旧值。因此一般不推荐这种顺序,除非配合延迟双删。

4. 使用消息队列保证最终一致

更新数据库后,将“删除缓存”的操作发送到消息队列,由消费者异步执行。如果删除失败,可以重试,直到成功。

优点:解耦、可重试、可靠性高。

缺点:引入 MQ 增加系统复杂度;存在短暂不一致窗口。

5. 订阅数据库 Binlog 异步删除

通过 Canal 等工具订阅 MySQL 的 Binlog,解析出数据变更后,发送消息删除对应缓存。

优点:业务代码无侵入,可靠性高,是很多大厂的实践方案。

缺点:架构复杂,需要维护 Canal、MQ 等组件。

6. 读写锁 / 分布式锁

在更新时加锁,保证同一数据的读写串行化。例如使用 Redisson 的读写锁:

  • 读操作加读锁
  • 写操作加写锁

优点:强一致性好。

缺点:性能下降明显,锁竞争激烈时成为瓶颈,一般只用于对一致性要求极高的场景。

如何选择

方案 一致性 性能 复杂度 适用场景
先更新库再删缓存 最终一致 绝大多数场景
延迟双删 最终一致 并发写较多
MQ 异步删除 最终一致 中高 对可靠性要求高
Binlog 订阅 最终一致 大型系统
分布式锁 强一致 金融级场景

面试回答要点

  1. 先指出问题本质:双写不一致源于并发下读写的交错执行,以及缓存删除失败。
  2. 给出主流方案:先更新数据库再删除缓存是基础,延迟双删是增强。
  3. 强调最终一致:在分布式系统中,强一致代价极高,通常追求最终一致。
  4. 补充兜底措施:设置缓存过期时间作为最后保障;删除失败时通过 MQ 重试。
  5. 结合场景:如果面试官追问,可以提到 Binlog 订阅方案,体现对生产实践的了解。

总结

Redis 与数据库的双写一致性没有银弹。Cache Aside + 延迟双删 + 缓存过期是性价比最高的组合,能覆盖绝大多数业务场景。对于一致性要求极高的场景,可以引入 MQ 或 Binlog 订阅来保证删除操作的可靠性。理解每种方案的取舍,比死记某一种“标准答案”更重要。

未经允许不得转载:任鹏个人博客 » Redis 缓存与数据库双写一致性问题怎么解决

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏