Skip to main content

缓存与数据库一致性?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

答题思路

回答话术

缓存与数据库一致性是指在使用缓存的情况下,保证缓存中的数据与数据库中的数据保持一致的问题

先写缓存再写数据库、先写数据库再写缓存、先删除缓存再写数据库这三种在实际工作中不建议使用,存在比较大的数据不一致隐患

可以根据业务场景选择下述缓存一致性方案:

  • 缓存双删:如果公司现有消息队列中间件,可以考虑使用该方案,反之则不需要考虑。
  • 先写数据库再删缓存:这种方案从实时性以及技术实现复杂度来说都比较不错,推荐大家使用这种方案。
  • Binlog 异步更新缓存:如果希望实现最终一致性以及数据多中心模式,该方案无疑是最合适的。

缓存与数据库一致性相关的几篇文章,我会以 12306 铁路购票系统 中购票扣减余量进行举例。

问题详解

1. 先写缓存再写数据库

两个用户购买了车站余票,假设余票有 17 张,两个用户扣减完还剩 15 张票。

如图所示,多个请求并发写入缓存和数据库,写请求 A 先更新 Redis 余票为 16。此时,写请求 B 将余票缓存更新 Redis 为 15,紧接着执行数据库更新为 15。这个时候,写请求 A 继续执行更新数据库操作,余票数据更新为 16。

这样就导致了多请求并发场景下,执行结果和咱们预期的结果不相符。

2. 先写数据库再写缓存

同上所诉,参考对应的业务场景和多请求并发场景,不同的是前者先更新缓存,后者先更新的是数据库,相同的是都存在并发问题,导致结果与预期并不相符。

3. 先删除缓存再写数据库

假设有两个并发的读写操作,一个是写操作,另一个是读操作。

  1. 并发读写的情况下,写操作首先删除缓存,接下来需要执行更新数据库操作。
  2. 读操作发生,由于缓存已经被删除,读操作不得不从数据库中读取数据。然而,由于写操作尚未完成,数据库中的数据仍然是过时的。
  3. 写操作这时需要更新数据库中的值,更新后 MySQL 数据库是最新的值。
  4. 读操作将从数据库中查询到的过时数据再回写到缓存。

在这种情况下,读操作获取到的是过时的数据,尽管写操作已经完成。因为缓存被删除,读操作不得不从数据库中读取旧值,而不是最新的值。

4. 先删除缓存再写数据库,再删除缓存

看着名字虽然有点长,但是如果换个词大家估计就懂了:缓存双删技术方案。

如果说上图的读请求回写缓存在写请求第二次删除缓存之前,那这种技术方案是比较好的,而且也不用引入过多复杂的中间件。

问题就在于,第二次删除缓存,不一定在读请求回写缓存之后。所以我们需要保证第二次删除要在请求回写缓存之后。

假设读请求回写缓存大概需要 300ms,那我们是否可以在写请求第二次删除缓存前进行一个延迟操作,比如睡眠 500ms 后再删除?这样就可以规避读请求回写缓存在第二次删除之后了。

这种方案理论上是可以的,不过把这个睡眠操作使用延迟队列或者引入三方消息队列去做。

最新技术架构流程如下所示:

如果消息队列更新缓存失败了呢?其实这一点还好,凭借消息队列客户端消费的重试规则,如果更新失败次数都达到客户端重试阈值还是不行,那一定是数据或者缓存中间件有问题。

当然,如果重试次数多了,也必然会面临缓存与数据库不一致的时间变长了,这个是需要清楚的。

通过该技术方案,可以很好达到缓存与数据库最终一致性。

5. 先写数据库再删除缓存

参考文章:✅ 先写 DB 再删除缓存解决缓存一致性?

6. 先写数据库,通过 BinLog 异步更新缓存

参考文章:✅ Binlog 配合 MQ 如何解决缓存一致性?