Redis第13讲——缓存不一致问题四种解决方案

1 篇文章 0 订阅

上篇文章介绍了缓存常用的三种设计模式,并且对使用最广泛的Cache Aside Pattern进行了深入的探讨,在比较先操作数据库or先操作缓存、删除缓存or更新缓存之后发现,先操作数据库再删除缓存是最优的选择,但是依然存在数据一致性问题,那么,本章在“先操作数据库再删除缓存”这种方案的基础上,给出3个更加完备的方案来解决缓存于数据库一致性问题。

一、同步删除

这个其实在上篇文章已经介绍过了,为了能够更好的对比下面三种方案,这里再介绍一下。

流程如下:

更新请求进来,我们先更新数据库再删除缓存,这里会存在两个问题:

  • 如果删除缓存失败,那么就会导致脏数据(ps:更新频率高的数据脏数据的窗口时间就小,反之窗口时间较大,不过删除失败的几率还是很小的)。
  • 并发场景下的脏数据:

  • 写线程A刚执行一次更新请求,删完缓存(上述例子可不是延迟双删,只是为了便于理解)。
  • 读线程B就发起请求查询数据,缓存未命中所以去查数据库查出数据=10。
  • 此时写线程A又发起了更新请求,把数据库的值更新为20,并再次删除缓存。
  • 读线程B将查出的10写入缓存,那么这时就出现了数据库和缓存不一致的情况(数据库=20,缓存=10)。

当然,由于更新数据库操作耗时一般比写缓存更久,所以上述例子发生的概率不大,但是还是有可能的,比如读线程B查完数据库后,写缓存前,读线程B所在的服务器发生了YGC,那么这时候读线程B可能就需要等待几十毫秒才能执行写操作。

ps:如果业务量不大,这种方案基本上也能满足需求。

二、延迟双删

这个我们就比较熟悉了,面试经常问,我们也经常会回答延迟双删,但在实际的开发中,这种采用的并不多,不过比方案一要更完善。

2.1 核心流程如下

 

  • 更新请求过来,先删缓存。
  • 更新数据库。
  • 等待一段时间再次删除缓存。

2.2 两次删除缓存的原因

2.2.1 第一次删除

第一次先删缓存的原因是缓存和数据库的更新不是一个原子操作,也就是会出现一个成功一个失败的情况。

如果写数据库成功了,但是删除缓存失败了,那么就会导致数据不一致。如果是写数据库失败了,那么缓存删除也就删除了,不会出现错误的数据。

但会有人问,第二次不也存在删除缓存失败的情况吗?没错,确实有失败的概率,但是我们延迟双删方案已经先做了一次删除,而第二次删除只是为了尝试解决因为读写并发导致的不一致问题。

如果没有第一次删除,只靠第二次,那么第二次要解决的可就不只是读写并发导致数据不一致的问题了,还要解决缓存删除失败的问题,所以,第一次删除的目的就是降低删除缓存失败带来的数据不一致问题。

2.2.2 第二次删除

我们现在假设没有第二次删除,也就是现在的逻辑是:先删缓存再写数据库,这种场景也在上篇文章介绍过:

那么这种问题解决起来也比较简单,那就让写线程A再删除一遍缓存就行了。

这样就能确保缓存中的脏数据被清理掉,避免后续的读操作读的到都是“脏数据”,所以,为了避免因为先删除缓存而导致的“读写并发问题”被放大的情况,引入了第二次删除缓存。

2.3 存在的问题

  • 第二次删除延迟时间难以确定,如果延迟10秒,那么这10秒可能就存在“脏数据”。

  • 主从数据库并发问题:

在主库写压力比较大的时候,主从之间的同步延迟甚至可能是分钟级别的,因为主从同步之间,存在并发的请求,这也会导致脏数据问题,因此,该方案在这种场景中也有明显问题。

三、监听binlog删除+重试

3.1 核心流程

  • 更新请求过来先更新数据库。
  • 缓存管理服务订阅binlog,将删除缓存消息推送至消息队列来删除缓存。
  • 如果删除失败则进行重试。

3.2 存在的问题

  • 脏数据窗口时间相比于同步删除来说较长。在收到binlog之前,他中间要经过:binlog从主库同步到从库、binlog从库到binlog监听组件、binlog从监听组件发送到MQ、消费MQ消息,这些操作都是有一定耗时的,可能是几十毫秒甚至几百毫秒。而同步删除可能1毫秒就搞定了。

  • 该方案强依赖于监听binlog的组件,如果该组件出现故障,则会导致大量脏数据。

  • 拆库拆表流程存在并发问题:假设表A在进行数据库拆分,部分读新库、部分读老库、增量数据同步双写,在灰度切读阶段,我们还是优先保障老库的流程,因此还是先写老库,如果写压力比较大,数据同步可能需要达到几秒,因此会出现以下问题:

不过整体来说该方案没有太大问题,这也是目前解决缓存和数据库不一致问题的主流方案。

四、缓存三重删除+数据校验+禁用缓存+强制读Redis主节点

可以看到上面三种方案或多或少都存在一定的问题,那么这种方案则是在上面三种方案基础上一步步优化而来的,核心流程如下图:

解析:

  • 更新数据库后同步删除缓存:解决方案三中异步binlog删除不一致时间比较长的问题。
  • 监听binlog异步删除缓存:这是整个方案的核心,binlog理论上是绝对不会丢的,这也保障该步骤一定能删除成功,如果失败,也能通过MQ不断重试。(ps:上述两个步骤已经保障了绝大多数场景下数据是正确的)
  • 缓存数据设置过期时间:前几种方案存在并发产生的脏数据有可能会存在很长时间,这时候可以用过期时间来进行兜底,那么时间是设置为多少合适呢,可以根据数据更新的时间来,比如有个数据最近一小时更新过了,那么我们就可以把它的过期时间设置为1个小时。
  • 延迟N秒后进行数据一致性校验:这步也很关键,例如方案三中的问题就可以用这个来解决,每一个更新请求都会做数据校验,如果不正确就做修复,这就保障了绝大多数并发导致的脏数据问题。
  • 存在数据库更新的链路禁用对应缓存:简单的说就是不要在更新数据库操作后马上去读缓存,这种操作很有可能是会存在脏数据的,因为更新完之后,马上读取这个时间间隔非常短,可能缓存还没删,或者存在短期内的不一致(数据还没修复)。
  • 强制读Redis主节点:这个看过我之前文章的应该很容易理解,就是Redis跟Mysql一样,也会有主从延迟,当将数据写入Redis后,马上去查Redis,可能由于读取的是Redis的从库,导致读的还是老数据。

这个链路确实比较长,但从这四种方案来看,这也确实是解决数据库和缓存不一致问题的最佳方案,但也存在问题,如果缓存中存在热点key呢?一旦被删除,那么就有可能会产生缓存击穿的问题,所以,任何的技术方案,都是一个权衡的过程,没有“完美”方案,只有“适合”方案。

 End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

  • 16
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值