redis缓存与数据库一致性问题解决

目录

1 数据不一致原因

2 解决方案

2.1 先更新数据库,再更新缓存场景

2.2 先更新缓存,再更新数据库场景

2.3 先删除缓存,再更新数据库的场景

2.4 先更新数据库,在删除缓存场景

3 方案总结


1 数据不一致原因

先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。

  1. 我是先更新缓存中的数据再更新数据库的数据;
  2. 还是修改数据库中的数据再更新缓存中的数据;

数据库的数据和缓存中的数据如何达到一致性?首先,可以肯定的是,redis中的数据和数据库中的数据不可能保证事务性达到统一的,这个是毫无疑问的,所以在实际应用中,我们都是基于当前的场景进行权衡降低出现不一致问题的出现概率

  • 更新缓存还是让缓存失效

更新缓存表示数据不但会写入到数据库,还会同步更新缓存; 而让缓存失效是表示只更新数据库中的数据,然后删除缓存中对应的key。那么这两种方式怎么去选择?这块有一个衡量的指标。

  1. 如果更新缓存的代价很小,那么可以先更新缓存,这个代价很小的意思是我不需要很复杂的计算去获得最新的余额数字。
  2. 如果是更新缓存的代价很大,意味着需要通过多个接口调用和数据查询才能获得最新的结果,那么可以先淘汰缓存。淘汰缓存以后后续的请求如果在缓存中找不到,自然去数据库中检索
  • 先操作数据库还是先操作缓存

当客户端发起事务类型请求时,假设我们以让缓存失效作为缓存的的处理方式,那么又会存在两个情况,

  1. 先更新数据库再让缓存失效
  2. 先让缓存失效,再更新数据库

前面我们讲过,更新数据库和更新缓存这两个操作,是无法保证原子性的,所以我们需要根据当前业务的场景的容忍性来选择。也就是如果出现不一致的情况下,哪一种更新方式对业务的影响最小,就先执行影响最小的方案

2 解决方案

2.1 先更新数据库,再更新缓存场景

当有两个线程A、B,同时对一条数据进行操作,一开始数据库和redis的数据都为tony,当线程A去修改数据库,将tong改为allen,然后线程A在修改缓存中的数据,可能因为网络原因出现延迟,这个时候线程B,将数据修改成了Mike、然后将数据库中的tony,也改成了Mike,然后线程A恢复正常,将redis中的缓存改成了allen,此时就出现了缓存数据和数据库数据不一致情况。不推荐

2.2 先更新缓存,再更新数据库场景

当有两个线程A、B,同时对一条数据进行操作,线程A先将redis中 的数据修改为了allen,然后CPU切换到了线程B,将redis中的数据修改为了mike,然后将数据库中的信息也修改了mike,然后线程A获得CPU执行,将数据库中的信息改为了allen,此时出现缓存和数据库数据不一致情况。不推荐

2.3 先删除缓存,再更新数据库的场景

当有两个线程A、B,同时对一条数据进行操作,当线程A进行修改缓存操作时,先删除掉缓存中的数据,然后去修改数据库,因为网络问题出现延迟,这时线程B需要去数据库中查询数据为tony,然后将数据更新到缓存中,线程A网络恢复,又将数据库数据修改为了allen,此时出现数据不一致。不推荐

2.4 先更新数据库,在删除缓存场景

一改一查场景:

当有两个线程A、B,线程A先去将数据库的值修改为allen,然后需要去删除redis中的缓存,当线程B去读取缓存时,线程A已经完成delete操作时,缓存不命中,需要去查询数据库,然后在更新缓存,数据一致性;如果线程A没有完成delete操作,线程B直接命中,返回的数据与数据库中的数据不一致,可能会短暂出现数据不一致情况,但最终都会一致。推荐

存在的问题:当数据过期或者初始化时,会出现数据不一致情况,也就是线程B从数据库中,查询到数据为tony,然后线程A将tony修改为了allen,然后去删除redis中的数据,然后线程B将读到的tony,更新到了数据库中,出现了数据不一致问题。

解决方案:对于不过期的数据我们要在上线的时候做好数据的预热,保证缓存命中。对于存在过期的数据,因为有过期时间,只会在特定的时间段内数据不一致,下次数据过期后,可以恢复,对于实时性要求不高时,可以接受。

两次修改场景:

当有两个线程A、B,线程A去修改数据库中的值改为allen,然后出现网络波动,线程B将数库中的值修改为了Mike,然后两个线程都会删除缓存,保证数据一致性。

3 方案总结

写完数据库后是否需要马上更新缓存还是直接删除缓存?

(1)、如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以马上更新缓存,但是如果对于那种写数据频繁而读数据少的场景并不合适这种解决方案,因为也许还没有查询就被删除或修改了,这样会浪费时间和资源

(2)、如果写数据库的值与更新缓存的值不一致,写入缓存中的数据需要经过几个表的关联计算后得到的结果插入缓存中,那就没有必要马上更新缓存,只有删除缓存即可,等到查询的时候在去把计算后得到的结果插入到缓存中即可。

所以一般的策略是当更新数据时,先删除缓存数据,然后更新数据库,而不是更新缓存,等要查询的时候才把最新的数据更新到缓存

下图展示了,比如针对缓存失效为例子,第一个尝试去更新数据库,第二步尝试让缓存失效,如果第二步对个某个key的缓存失效,可以发送失败消息到mq中,然后自己消费mq的消息不断的重试让缓存失效,mq保证消息持久性和缓存和数据库的数据最终一致性。

方案还可以是双删

先删除redis,再更新数据库,回头再删除redis,因为更新的时候还会有旧数据存在redis当中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值