缓存一致性问题

目录

1、缓存一致性问题是什么

2、解决方案

2.1、只写DB,不写Cache,依赖下次查询

2.2、先写数据库,再写缓存

2.3、先写缓存,再写数据库

2.4、先删除缓存,再更新数据库

2.5、先删除缓存,再更新数据库,再删除缓存


 引言

对于互联网业务来说,传统的直接访问数据库方式,主要通过数据分片、一主多从等方式来扛住读写流量,但随着数据量的积累和流量的激增,仅依赖数据库来承接所有流量,不仅成本高、效率低、而且还伴随着稳定性降低的风险。鉴于大部分业务通常是读多写少(读取频率远远高于更新频率),甚至存在读操作数量高出写操作多个数量级的情况。因此,在架构设计中,常采用增加缓存层来提高系统的响应能力,提升数据读写性能、减少数据库访问压力,从而提升业务的稳定性和访问体验。

根据 CAP 原理,分布式系统在可用性、一致性和分区容错性上无法兼得,通常由于分区容错无法避免,所以一致性和可用性难以同时成立。对于缓存系统来说,如何保证其数据一致性是一个在应用缓存的同时不得不解决的问题。

需要明确的是,缓存系统的数据一致性通常包括持久化层和缓存层的一致性、以及多级缓存之间的一致性,这里我们仅讨论前者。持久化层和缓存层的一致性问题也通常被称为双写一致性问题,“双写”意为数据既在数据库中保存一份,也在缓存中保存一份。对于一致性来说,包含强一致性和弱一致性,强一致性保证写入后立即可以读取,弱一致性则不保证立即可以读取写入后的值,而是尽可能的保证在经过一定时间后可以读取到,在弱一致性中应用最为广泛的模型则是最终一致性模型,即保证在一定时间之后写入和读取达到一致的状态。对于应用缓存的大部分场景来说,追求的则是最终一致性,少部分对数据一致性要求极高的场景则会追求强一致性。

1、缓存一致性问题是什么

什么是缓存一致性:

  缓存一致性是指缓存数据与落地数据一致,缓存一致性是并发场景下使用缓存带来的问题,非并发系统或者不使用缓存,都不会存在缓存一致性的问题。

  缓存作为落地数据的备份,作用是以供快速读取热点数据。缓存设计的一个指标就是热点数据命中的概率。

为什么会出现缓存一致性的问题

  更新缓存的数据与更新数据库的数据是两个独立的步骤,并发情况下这两个操作的顺序会出现不同的可能,每一种顺序都有可能导致的缓存数据与落地数据的不一致。

   我们知道,缓存的工作原理是先从缓存中获取数据,如果有数据则直接返回给用户,如果没有数据则从慢速设备上读取实际数据并且将数据放入缓存。

同步:

异步:

但是,这样的架构是存在问题的,因为数据库与缓存是不同的组件,操作必须有先后顺序,无法像数据库的事务一样满足ACID的特性,所以就会出现数据在缓存中与在数据库中不一致的问题

缓存一致性问题的表现:同一份数据,缓存中的数据与数据库中的数据不一致,那么上升到业务层面就有着千奇百怪的现象了,比如每次读都是读的老数据,或者每次读是一份过时的数据等。

2、解决方案

对于写入操作:

  1. 只写DB,不写Cache,依赖下次查询

  2. 先写DB,(同步/异步)再写Cache

  3. 先写Cache,再写DB

对于更新操作:

  1. 先删除Cache,再更新DB

  2. 先删除Cache,再更新DB,再删除Cache

2.1、只写DB,不写Cache,依赖下次查询

这种是我们常见的设计方案,这种方案只写数据库不写缓存,依赖下一次请求从数据库取出数据再放入缓存。细心的读者已经发现了,这种设计有可能引发新的问题:缓存击穿(复习缓存击穿:DB有数据,Cache无数据,瞬间流量将DB击穿)。

这种可能性是存在的,但是可能性比较小,因为缓存击穿的前提条件是大量请求透过缓存打入数据库层,但是因为我们讨论本次小标题的前提条件是新写入,一般不会有很大的瞬间流量进来。

2.2、先写数据库,再写缓存

这种也是我们常见的设计方案,先写数据库,再写缓存

所以在这种场景下,线程1再去读数据的时候,读数据则优先走缓存,缓存此时值为1,所以读到的值是1,此时线程1懵逼了啊......我刚才不是更新成2了吗?

  在面临缓存穿透的时候,我们其中一个解决方案是:查询数据库如果没有数据,则约定一个空数据格式放入缓存中,当再次查询的时候,先查询缓存,发现是一个空数据格式,则直接返回空,避免数据库被瞬间流量击垮。在这个方案下,还有第二个步骤,当数据保存后,需要主动将数据放入缓存,以便下次能够查询。

所以如果你的系统中如果有做缓存穿透的防护,有可能你写完数据库后还需要记得写缓存。

2.3、先写缓存,再写数据库

顾名思义,就是一个写操作,先写缓存,成功后,再写数据库。

那么,如果写数据库失败呢?

如果写失败了,在下次读的时候那么就会读取到脏数据的情况。

如果写数据库失败,有两种方案

  1. 删除缓存

  2. 异步任务继续写数据库

这两个方案都有问题!

下面我们挨个分析。

    删除缓存。如果删除缓存失败呢?再用异步任务重试删除?那你是否有考虑重试的时候这种短暂不一致的情况?还是说接受这种数据不一致的情况?系统复杂度被你提高了多少?

     异步任务继续写数据库。异步任务如果写失败呢?重试?重试也一直失败呢?重试任务落库+定时任务兜底?可以,那么,短暂的数据不一致是否接受?系统复杂度被你提高了多少?

所以,这种先写缓存再写数据库的方案一般不会正式使用,一旦出问题,很难保证数据的最终一致性。

接下来我们讨论一下更新数据的情况

2.4、先删除缓存,再更新数据库

  这种方式也比较容易理解,先删除缓存数据,再更新DB的数据,如果删除缓存失败了,直接返回失败;如果更新DB失败了,影响的也只是删除缓存而已,下次查询的时候重新种一次即可。

  那如果,会不会因为删除了缓存的数据,从而导致DB被击穿呢?这种可能性是存在的,但是可能性比较小。

  再说了,这种方案真的可以解决问题吗?如果在删除缓存后,马上有新线程查询缓存,新线程发现缓存不存在(刚被删),新线程查询数据库后将数据放于缓存,老线程删除数据库成功。此时数据库无数据,缓存有数据。

2.5、先删除缓存,再更新数据库,再删除缓存

基于2.4,在这个基础上可以做出一些改进,那就是延迟双删。

延迟双删的流程: 删除缓存->删除DB->延迟一段时间再删除缓存。

延迟双删能解决大部分的问题,但是在极端情况下,还是会出现问题,造成数据不一致。

这里存在一个问题,延迟一段时间,是延迟多久?1s?3s?这是一个经验值,一般情况是1s~2s。具体取值根据监控实际情况而定。那既然是估计值,那么就一定存在误差,所以必然极端情况下的数据不一致问题。

解决这个问题的方法之前也说了,监听数据库binlog增量数据更新缓存,或者还可以使用异步消息等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Upaaui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值