浅谈缓存与数据库的一致性问题

前言

面试经常问缓存与数据库的数据如何保持一致性,很有必要研究一下这个问题,首先提前声明,以下分析都是要基于提高吞吐量的前提下进行,要是抛弃吞吐量这个前提,最直接的办法就是上分布式锁,让所有读写请求都串行化,肯定可以解决了数据库的一致性问题,但吞吐量就很显然会下降!

本质

个人觉得,解决缓存与数据库不一致性的问题可以细分为下面这两个问题

  • 操作数据库肯定是更新数据,那么操作缓存是更新数据还是删除数据呢?
  • 先操作数据库还是先操作缓存呢?

下面我们逐个问题进行分析

Cache的写机制和读机制

我们首先要对现在普遍的Cache的写机制和读机制

读机制

读机制只有一种,称为read-through,该过程的机制如下

  1. 先尝试缓存中读取数据
    1. 如果数据存在,直接返回
    2. 如果数据不存在,访问DB,取出数据,然后将数据放进缓存中

写机制

写机制有两种

  • write-through:数据更新时,同时更新数据库与缓存
  • write-back:数据更新时,仅仅对缓存进行修改,然后一段时间间隔之后才将缓存里面的数据回写进数据库中

认识完两种机制之后,下面对两个问题进行分析

先操作数据库还是先操作缓存?改缓存还是删除缓存?

对于这种问题,一定要结合场景去进行分析,没有场景就纯属自嗨而已。

缓存与数据库的不一致性问题,要从两个因素去考虑

  1. 并发量
  2. 操作失败

并发量限制

超高并发量(秒杀系统)

第一个场景:经典秒杀系统(超高并发)

对于秒杀系统,这种高并发量,如果我们先操作数据库再操作缓存,结果就是数据库被请求打挂掉,所以,对于秒杀系统,一定是要去先操作缓存,并且尽量要在缓存中运算,也就是采用改缓存的形式,采用write-back的机制来进行回写数据库!

因此一致性问题要从有高并发,但还不至于将DB打挂的角度去思考才有深度。。。

高并发量(但不至于打挂DB)

第二个场景:某大型企业的电商系统(并发量高,但不至于将DB打挂掉)

由于不会对DB打挂掉,因此操作数据库再操作缓存和操作缓存再操作数据库都是有可行性的,同样对于改缓存和删除缓存也都是有可行性的

在这个场景下,先探讨一下究竟是对缓存删除、还是对缓存修改

缓存删除与缓存修改

排除掉所有失败的情况,缓存修改会带来旧数据覆盖新数据问题

比如,对于先操作数据库后修改缓存情况下,两个线程A和B对数据库同一条记录进行并发修改,并且A先执行,那么正确的顺序应该是A修改缓存,然后再到B修改缓存,但高并发的情况下可能会出现B修改缓存,再到A修改缓存这种情形,这就会新数据被旧数据覆盖了;如果是先修改缓存后操作数据库,那么缓存里面的数据应该要以最后入库的数据为准,最后入库代表最新修改嘛,这很好理解,那么在高并发情况下,如果不加锁,缓存修改不就像摸奖一样,都不管数据库那边怎么修改的,谁最后修改了缓存就算谁的,比如A修改了缓存,还没修改数据库,之后B修改了缓存又修改了数据库,此时缓存中的数据为B的修改数据,后面A继续修改数据库之后,缓存数据就不会变为A修改的数据了。

遇到上面的情形怎么解决?

个人觉得唯一解决的方式就是加锁,但这有意义吗?需求为高吞吐量,加锁不就会让吞吐量降低、得不偿失了?

所以,一般对于缓存,除了上面的那种需要在缓存中运算的秒杀系统之外,都不建议采用缓存修改的方式!!

如果采用缓存删除,无论哪个线程先执行、只要缓存被删掉,一般场景下,后面的线程都会从数据库里读取最新的数据,所以采用缓存删除策略是较好的!

探讨完对缓存的操作之后,下面来分析一下,先操作缓存,还是再改数据库

先操作缓存,还是先操作数据库?
1、先操作缓存、后改数据库

如果采用先删除缓存,后改数据库的方式,会出现什么问题呢?

同样以两个线程A、B为栗子

如果A删除了缓存之后,在改动数据库的过程中,B此时访问了缓存,发现缓存中没有数据,就会从数据库中读取数据然后又放进缓存中,此时就出现了并发问题,假如每个线程完成数据库操作的时间需要耗费10S,那么在这10S的时间内都可能会出现并发问题,并且一旦发生一次,将会影响后面的线程,因为旧的缓存被添加进去了!并且要等到下一次删除缓存或者缓存过期才会恢复为正确数据!!

2、先改动数据库、后操作缓存

如果采用先改数据库,后删除缓存的形式

如果A改动了数据库,但还没有删除缓存,此时B进来了就会访问旧的缓存,此时就出现了并发问题,假如每个线程操控缓存的时间需要耗费5S,在这5S的时间内都可能会出现并发问题,但也仅仅只出现在这5S内,当A完成删除缓存操作之后,后面线程会从数据库中读取,然后添加进缓存中,此时缓存就是最新的数据。

两者进行对比,孰优孰劣就很明显了,对于性能方面上,肯定是先改数据库更优!

  • 先操作缓存,后改数据库,可能出现并发问题的时间区段长度为完成数据库操作所需的时间,而且在该时间内,一旦出现了并发问题,只能等后续的缓存过期或者删除缓存才能恢复

  • 而先改数据库,可能出现并发问题的时间区段长度仅仅为完成删除缓存所需要的时间(通常操作缓存都比操作数据库要快),而且在该时间内,如果出现了并发问题,后面一旦删除缓存操作完成,就结束了,后面的线程去访问该缓存,会去数据库取新的数据然后放进缓存中

  • 并且,关键的一点是,如果先操作缓存,然后再改动数据库,如果发生了并发问题,请问前面对缓存的操作还有意义吗?显然是没有意义的,相当于仅仅就做了一次修改数据库而已!

经过上面一轮分析

已经得出结论了,最佳的结果就是先操作数据库,后删除缓存

这也是著名的Cache-Aside-Pattern所采用的保持缓存与数据库的一致性的策略,翻译过来就为边缘缓存模型,该模型的作用就是按需将数据从数据存储加载到缓存中

关于边缘网络模型:可以参考这篇CSDN: Cache-Aside Pattern_张申傲的博客-CSDN博客

补充:扯淡的延迟双删

首先来说明一下延迟双删解决了什么问题

延迟双删解决了先删除缓存,再改动数据库之后的并发问题,前面提到了,先删除缓存,后改动数据库会导致未完成数据库的改动就删除了缓存,导致后续线程因为缓存被删除,访问了数据库取出旧的数据,将旧数据又添加了缓存里面了;其本质就是再完成改动数据库之后,线程过一段时间之后再次删除缓存,也就是延迟一段时间再删除缓存,因为删除了两次,又发生了延迟,称为延迟双删

为什么要延迟呢?这是因为不知道线程何时会将旧数据添加进缓存中,假如A、B线程,A删除了缓存,B立马访问缓存,发现没有,然后去访问DB,拿到了旧数据,此时CPU又调度了A,A完成了数据库操作,但A不知道B什么时候将旧数据添加进去,因此也就有了延时操作。

为什么说延迟双删扯淡呢?

  • 延迟的时间难以确定,随着并发量越高,延迟的时间就越久
  • 降低了服务器性能,现在都高并发情况了,还要进行延迟,吞吐量不更加低了吗?

操作失败因素

现在基于前面根据并发量的限制所得出的结果继续讨论操作失败该如何解决!

现在的步骤是:先更新数据库,然后删除缓存,此时如果更新数据库成功了,但删除缓存失败了,此时缓存里面记录的还是旧的缓存信息,那这时候就会出现问题了

针对这种问题,现在解决的方案个人了解到只有做补偿操作

而补偿操作又有很多种实现,下面简单介绍两种

  • 消息队列,当更新数据库成功后,将删除缓存的消息推到消息队列中,通过消息队列来完成补偿操作
  • 监听数据库的Binlog,只要更新数据库成功,Binlog就会记录,通过监听数据库的Binlog,来进行补偿行为的发生

至此一个完整的方案大概就出来

完整的方案

使用缓存边缘模型 + 补偿机制 已经可以解决大部分的问题,当然,最终的手段还是采用分布式锁进行串行化

参考

Cache-Aside Pattern_张申傲的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值