loong博客

如何保证Redis缓存与数据库一致性?

目录

  • 为什么会出现Redis缓存与数据库不一致的情况?
  • 旁路缓存
  • 延迟双删
  • 消息队列(最终一致性)
  • 监听数据库Binlog(强一致性)

为什么会出现Redis缓存与数据库不一致的情况?

保证Redis缓存与数据库的一致性,是在高并发系统中必须面对的挑战。没有一种“银弹”可以解决所有场景,我们需要根据业务场景、一致性要求和技术成本来选择合适的策略。我们对缓存和数据库的更新不是一个原子操作,它总是一个先一个后。在高并发环境下,无论先更新谁,都可能因为线程交叉执行或操作失败,导致数据不一致。

旁路缓存

旁路缓存策略是最常用、最基础的策略,其核心思想是:缓存是数据库的辅助,所有写操作都直接与数据库交互。

旁路缓存读写操作

  • 读操作
  1. 从缓存读取数据

  2. 如果缓存命中,直接返回数据

  3. 如果缓存未命中,从数据库读取数据。将数据库读出的数据写入缓存,然后返回数据

  • 写操作
  1. 更新数据库

  2. 删除缓存

    为什么是”删除缓存”而不是”更新缓存”?

    性能浪费: 如果这个数据在下次被读取前又被多次更新,那么中间状态的缓存更新就是不必要的,浪费了资源。

    脏数据风险: 在并发写时,可能会出现线程A更新数据库后,线程B又更新了数据库,但更新缓存的顺序却是B先A后,导致缓存中是老数据(A的数据)。

旁路缓存不一致场景

旁路缓存策略在绝大多数情况下它是可靠的,但在一种极端的并发场景下会出问题:

  1. 时刻1:缓存刚好失效

  2. 时刻2:线程A发起读请求,未命中缓存,去查询数据库(得到旧值)(读操作步骤1

  3. 时刻3:线程B发起写请求,更新了数据库。(写操作步骤1

  4. 时刻4:线程B删除缓存。(写操作步骤2

  5. 时刻5:线程A将之前读到的旧值写入缓存。(读操作步骤2

在此场景下,缓存中变成了脏数据(旧值),并且会一直持续到下次缓存过期或被更新。这个场景发生的概率很低,因为它要求缓存失效和并发读写同时发生,并且步骤2的读数据库操作必须在步骤3的写数据库操作之前开始,但步骤5的写缓存操作又必须在步骤4的删除缓存操作之后完成。由于数据库的写操作通常比读操作更慢(加锁等),所以这个时间窗口非常小。

延迟双删

延迟双删策略是在”先更新数据库, 再删除缓存”的基础上,增加一个休眠和二次删除的步骤。

  1. 更新数据库

  2. 删除缓存

  3. 休眠一个短暂的时间(如几百毫秒到1秒)

  4. 再次删除缓存

    为什么休眠?

    为了确保主库的更新已经完成,并且可能产生的旧缓存(如上文旁路缓存极端并发场景时刻5)已经被设置。

    为什么”再次删除”?

    第二次删除就是为了清理这些”脏”缓存。

延迟双删缺点

  1. 休眠时间难以确定,可能删不掉或等待过久

  2. 降低了吞吐量。

  3. 第二次删除仍可能失败

消息队列(最终一致性)

通过消息队列保证最终一致性,通常是在”先更新数据库,再删除缓存”的基础上,结合延迟双删和消息队列的重试机制,来尽可能地保证缓存被删除,从而达到最终一致性。将第二次删除操作作为消息发送到消息队列,由消费者重试,确保删除成功。

  1. 更新数据库

  2. 删除缓存(第一次删除)

  3. 向消息队列发送一条延迟消息(比如延迟1秒),消息内容为要删除的缓存key

  4. 消息队列在延迟时间过后,将消息投递给消费者

  5. 消费者再次删除缓存(第二次删除)

注意:这里的延迟时间(1秒)需要根据实际业务场景来设定,通常需要大于数据库主从同步的时间(如果用了主从)以及读请求的耗时。

Q&A

  1. 第一次删除失败怎么办?

    由第二次删除来弥补。

  2. 发送延迟消息失败怎么办?

    重试发送,或者记录日志,由人工干预。

注意:这个方案并不能保证100%的强一致性,但是能够极大地提高一致性。同时,我们还可以通过设置缓存的过期时间来做兜底,即使出现不一致,数据也会在过期时间后自动清除。

消息队列实现一致性优点

保证了操作的最终成功,可靠性高

消息队列实现一致性缺点

系统复杂度增加,需要维护消息队列

监听数据库Binlog(强一致性)

通过监听数据库 Binlog 进行同步,这是最优雅、对业务代码侵入性最小的方案,也是大厂普遍采用的方案。

  1. 业务代码正常更新数据库。

  2. 数据库的变更会记录在 Binlog 中。

  3. 一个中间件(如阿里开源的 Canal)模拟数据库从库,监听并解析 Binlog。

  4. Canal 解析出需要删除的缓存 Key,将其发送到消息队列或直接调用 Redis 删除缓存。

  5. 从消息队列中消费变更事件,并执行缓存删除操作。

监听数据库Binlog实现一致性优点

  1. 业务代码极致简化。业务层不再关心缓存删除逻辑,只需要操作数据库。

  2. 高性能。数据库和缓存的同步是异步的,不影响主流程。

  3. 高可靠。基于 Binlog,顺序性和可靠性有保障。

监听数据库Binlog实现一致性缺点

  1. 系统架构最复杂,需要引入并维护 Canal 等组件。

  2. 同步有一定延迟,是最终一致性。