Redis-缓存一致性问题

发布时间 2023-12-17 23:58:34作者: 轻寒

要想保证缓存和数据库「实时」一致

当数据发生更新时,我们不仅要操作数据库,还要一并操作缓存,数据库和缓存都更新,又存在先后问题,那对应的方案就有 2 个:

  1. 先更新缓存,后更新数据库
  2. 先更新数据库,后更新缓存

先不考虑并发问题,正常情况下,无论谁先谁后,都可以让两者保持一致,但现在我们需要重点考虑「异常」情况。

因为操作分为两步,那么就很有可能存在「第一步成功、第二步失败」的情况发生。

1) 先更新缓存,后更新数据库

如果缓存更新成功了,但数据库更新失败,那么此时缓存中是最新值,但数据库中是「旧值」。

虽然此时读请求可以命中缓存,拿到正确的值,但是,一旦缓存「失效」,就会从数据库中读取到「旧值」,重建缓存也是这个旧值。

这时用户会发现自己之前修改的数据又「变回去」了,对业务造成影响。

2) 先更新数据库,后更新缓存

如果数据库更新成功了,但缓存更新失败,那么此时数据库中是最新值,缓存中是「旧值」。

之后的读请求读到的都是旧数据,只有当缓存「失效」后,才能从数据库中得到正确的值。

这时用户会发现,自己刚刚修改了数据,但却看不到变更,一段时间过后,数据才变更过来,对业务也会有影响。

如何保证两步都执行成功?

前面我们分析到,无论是更新缓存还是删除缓存,只要第二步发生失败,那么就会导致数据库和缓存不一致。

解决方案:

  • 更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,解决方案是加「分布锁」,但这种方案存在「缓存资源浪费」和「机器性能浪费」的情况
  • 采用「先更新数据库,再删除缓存」方案,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据最终一致

但如果有主从库延迟而导致的问题:

问题一:「先删除缓存,再更新数据库」导致不一致的场景,2 个线程要并发「读写」数据,可能会发生以下场景:

  1. 线程 A 要更新 X = 2(原值 X = 1)
  2. 线程 A 先删除缓存
  3. 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
  4. 线程 A 将新值写入数据库(X = 2)
  5. 线程 B 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。

解决方案:在线程 A 删除缓存、更新完数据库之后,先「休眠一会」,再「删除」一次缓存(延迟时间要大于「主从复制」的延迟时间)。

问题二:是关于「读写分离 + 主从复制延迟」情况下,缓存和数据库一致性的问题,如果使用「先更新数据库,再删除缓存」方案,其实也发生不一致:

  1. 线程 A 更新主库 X = 2(原值 X = 1)
  2. 线程 A 删除缓存
  3. 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
  4. 从库「同步」完成(主从库 X = 2)
  5. 线程 B 将「旧值」写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在主从库中是 2(新值),也发生不一致。

解决方案:线程 A 可以生成一条「延时消息」,写到消息队列中,消费者延时「删除」缓存(延迟时间要大于线程 B 读取数据库 + 写入缓存的时间)。