首页 > 数据库 >如何保证数据库和缓存双写一致性

如何保证数据库和缓存双写一致性

时间:2022-08-26 15:11:06浏览次数:81  
标签:缓存 请求 删除 数据库 先写 一致性 数据 双写

    数据库和缓存(redis)双写一致性问题,不管你用何种语言,尤其是在高并发的场景下,这个问题会很容易被放大。   无论是在工作中,还是在面试中遇到这种问题的概率都非常大,因此在这里跟大家探讨下。   方案一,也是最常见的方案   通常,我们使用缓存是为了提高查询的性能,大多数情况下,我是用一下方案:

 

  1.   用户请求过来,先查缓存有没有数据,如果有直接返回,提高性能
  2.   如果缓存没有,再查询数据库
  3.   如果数据库有数据,则查询出来,放入缓存,供后续请求获取
  4.   如果数据库也没有,则返回空

   这个方案很常见,不仔细思考,好像没有问题,但是我们忽略了一个非常重要的细节,如果查询完数据库,放入缓存之后,这个数据又被立马更新了,那我们该如何更新缓存呢?

  如果不过更新,等缓存到过期时间,自动失效,那么这段时间,缓存和数据库就有数据不一致的问题。

  所以必须更新缓存,如何更新,目前有以下4个方案:

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

  接下来,讲一下这4种方案。

  先写缓存,再写数据库

  这个比较容易想到的方案,在更新请求种,直接更新缓存,我们先说下,先写缓存,再写数据库的情况。

 

  一个更新请求过来,如果刚写完缓存,发生了网络异常,导致写数据库失败了,结果就是缓存是最新数据,但是数据库没有,这样缓存就是脏数据,一致性问题就出现了。为了提高性能使用了缓存,但是现在缓存查出来的是假数据或者旧数据,就没有意义了,因此这个方案在生产中很少使用。

  

   先写数据库,再写缓存

   该方案在并发不高的项目中,比较常用。

 

 更新请求,先写数据库,在写缓存,可以避免上面方案中,出现假数据的问题。

如果写数据库,和写缓存在同一个事务了,当写缓存失败,我们会对数据回滚,这样会保证数据库缓存一致,这样在并发小的时候,对接口性能要求不高的系统,可以这么使用。

但是高并发业务场景中,写数据库和写缓存,都属于远程操作,为了防止出些大事务,造成死锁,通常情况下,建议写数据库和写缓存不要放在同一个事物中。

也就是说这个方案中,写数据库成功,但是写缓存失败,是不会回滚数据库的。

这样就出现缓存不一致的问题了。

如果觉得上面所说的情况太牵强,高并发情况下,还会出现以下问题 :

假设高并发场景,有两个写请求:a和b,他们同时请求到系统,其中a获取写入旧的数据,b是写入新的数据

1.a请求先到,刚写完数据库,由于网络问题,卡顿一下,还没来得及写缓存

2.b请求这个时候也到了,先写了数据库

3.接下来,b顺利写了缓存

4.此后,a卡顿结束,也写了缓存

很显然,数据库里面存的是b的新数据,而缓存却被a的旧数据覆盖了。

由此可见,在高并发的场景中,先写数据库,再写缓存,这套方案问题挺多的,也不太建议使用。

先删缓存,在写数据库

 上面的方案,直接更新缓存问题比较多。

那么,我们换一种思路,不去更新缓存,而是直接删缓存。

先看看,先删缓存的情况:

 

 这种虽然简单,但是也有会有一些问题:

高并发下的问题:

对同一个数据,同时有一个读请求c,还有一个更新请求d到系统,如图所示:

 

1.写请求d先到,把缓存删除,但由于网络原因,卡顿一下,还没来得及写数据库

2.这个时候查询请求c过来了,先查缓存发现没有数据,然后去查数据库,有值,但是是旧数据。

3.请求c将旧数据,更新到缓存中。

4.此时,写请求d卡顿结束,把新数据写入数据库。

在这个过程中,同样会导致数据库缓存不一致问题。(图中有问题,步骤7写入旧值,步骤9要删掉)

 那么怎么解决呢?

缓存双删,如下图所示:

 

这个方案不同之处,就是在写入数据库之前删除一次,写完数据后,再删除一次。

还有一个关键的地方,就是第二次删除,并非马上删除,而是要在一定时间间隔(如:500ms)之后删除。

这是为了保证第二次删除操作在写请求d卡顿结束,把新的值写入数据库后,查询请求c查到旧值写入缓存后执行。

 

先写数据库,再删除缓存

从前面得知,先删缓存,再写数据库,在并发的情况下,也可能会出现缓存和数据库的数据不一致的情况。

 

在高并发场景中,过程如下:

1.写请求e先写数据库,由于网路卡顿,没来得及删除缓存。

2.读请求f查询缓存,发现有数据,直接返回

3.写请求e删除缓存

这个过程,只有读请求f读了一次旧数据,后来e请求及时删除了缓存,看起来可以接受。

如果是还有另外一种情况呢?

1.读请求f查询缓存,发现有数据,直接返回。

2.写请求e先写数据库,再删除缓存。

看起来也没问题?

但是如果是下图显示的情况:

 

 

 

   1.刚好缓存过期,自动失效

  2.请求f从数据库获取旧值,写入缓存前,发生卡断

  3.写请求e先写数据库,再删除缓存

  4.f请求结束卡顿,把旧值写入缓存

这时,数据库和缓存也发生不一致的问题。

但这种情况还是比较少的,需要同时满足以下条件才可以:

    1. 缓存刚好自动失效。
    2. 请求f从数据库查出旧值,更新缓存的耗时,比请求e写数据库,并且删除缓存的还长。

由此可见,系统同时满足上述两个条件的概率非常小。

推荐大家使用先写数据库,再删缓存的方案,虽说不能100%避免数据不一致问题,但出现该问题的概率,相对于其他方案来说是最小的。

 删缓存失败怎么办?

 其实先写数据库,再删缓存的方案,跟缓存双删的方案一样,有一个共同的风险点,即:如果缓存删除失败了,也会导致缓存和数据库的数据不一致。

那么就需要加重试机制。这个后面再开一篇讨论。

原文地址:https://mp.weixin.qq.com/s/mO819q_r9qLVuRBE8cjx1Q

标签:缓存,请求,删除,数据库,先写,一致性,数据,双写
From: https://www.cnblogs.com/meixiaoyu/p/16626532.html

相关文章

  • Redis获取缓存异常:redis java.util.ArrayList cannot be cast to java.lang.String
    Redis获取缓存异常:redisjava.util.ArrayListcannotbecasttojava.lang.String在使用redis缓存数据时,增加一个配置类,修改key序列化器为string@Configurationpublic......
  • Spring Boot 2.x基础教程:进程内缓存的使用与Cache注解详解
    随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring3开......
  • Spring Boot 2.x基础教程:EhCache缓存的使用
    在SpringBoot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),SpringBoot根据下面的顺序去侦测缓存提供者:GenericJCache(JSR-107)(EhCache3,Haze......
  • 解决Vuex刷新页面数据没缓存的问题
    监听页面是否刷新,如果页面刷新了,将state对象存入到sessionStroage中。页面打开之后,判断sessionStorage中是否存在state对象,如果存在,则说明页面是被刷新过,将sessionStor......
  • 综合案例-黑马旅游网_分类数据展示缓存优化
    综合案例-黑马旅游网_分类数据展示缓存优化分析发现,分类的数据在每一次页面加载后都会重写请求数据库来加载对数据库压力比较大而且分类的数据不会经常产生变化所有可......
  • spring boot中的缓存管理
    springboot默认的缓存管理常用的缓存注解@EnableCaching:在类上使用,表示使用基于注解的方式进行缓存管理@Cacheable:用在类或者方法上。该注解用在方法上时,在方法执行......
  • 常见的复制模型&分布式系统的挑战 事务,一致性与共识
    Replication(上):常见的复制模型&分布式系统的挑战https://mp.weixin.qq.com/s/LB5SR4ypQwDxzueI1ai2KgReplication(下):事务,一致性与共识https://mp.weixin.qq.com/s/O9Z5e_B......
  • redis删除缓存时遇到的问题
    一、redis查询key的方式redis常用两种方式用于key的精确/模糊匹配 1.KEYSpattern keyspattern用于匹配pattern所有key,会返回当前库里所有匹配上......
  • tidb 小表缓存
    TiDB在v6.0.0版本中引入了缓存表功能。该功能适用于频繁被访问且很少被修改的热点小表,即把整张表的数据加载到TiDB服务器的内存中,直接从内存中获取表数据,避免从TiKV......
  • 缓存三大问题及解决方案
    1.缓存来由随着互联网系统发展的逐步完善,提高系统的qps,目前的绝大部分系统都增加了缓存机制从而避免请求过多的直接与数据库操作从而造成系统瓶颈,极大的提升了用户体验和......