0
点赞
收藏
分享

微信扫一扫

uniapp 地图跳转到第三方导航软件 直接打包成apk

目录

什么是缓存双写一致

更新缓存还是删除缓存?

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

场景描述

解决方案:延时双删策略

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

场景描述

解决方案:重试机制+引入MQ

为什么要引入MQ


什么是缓存双写一致

只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题。我们需要保证redis跟数据库的中的数据保持一致,返回正确的数据。

更新缓存还是删除缓存?

 如果更新缓存,在并发写时,可能出现数据不一致。

假设现在同时有请求A和请求B进行更新操作,那么会出现

  • (1)线程A更新了数据库
  • (2)线程B更新了数据库
  • (3)线程B更新了缓存
  • (4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

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

场景描述

A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时)B突然出现要来读取缓存数据。此时redis里面的数据是空的,B线程来读取,先去读redis里数据(已经被A线程delete掉了)。

上述场景出现的问题:

  1. B从mysql获得了旧值:B线程发现redis里没有(缓存缺失)马上去mysql里面读取,从数据库里面读取来的是旧值
  2. B会把获得的旧值写回redis:获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能又被写回了)

最后:A线程更新完mysql,发现redis里面的缓存是脏数据

时间线程A线程B出现的问题
t1请求A进行写操作,删除缓存后,工作正在进行中......

A还更新完mysql,致B读到了旧值


线程B遵守回写机制,把旧值写回redis,导致其它请求读取的还是旧值,A白干了。

t2

缓存中读取不到,立刻读mysal,由于A还没有对mysal更新完,读到的是旧值。


 还把从mysgl读取的旧值,写回了redis

t3更新mysql数据库的值,overredis是被B写回的旧值
mysql是被A更新的新值
出现了,数据不一致问题。

解决方案:延时双删策略

为什么要sleep一段时间? 

 加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,然后,线程A再进行删除。所以,线程A Sleep的时间,就需要大于现场B读取数据再写入缓存的时间。这样一来,其他线程读取数据时,会发生缓存缺失,所以会从数据库中读取最新值。因为这个方案会在第一次删除缓存值后,延迟一段时间再去进行删除,所以我们也把它叫做"延迟双删"

休眠多久呢?这个时间怎么确定呢? 

  1. 在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可
  2. 这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据

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

场景描述

时间线程A线程B出现的问题
t1删除数据库中的值

t2缓存中立刻命中,此时B读取的是缓存旧值。A还没有来得及删除缓存的值导致B缓存命中读到旧值。
t3更新缓存的数据,over

异常原因:假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值

解决方案:重试机制+引入MQ

  • 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)
  • 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新
  • 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
  • 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员
     

为什么要引入MQ

  1. 在应用程序将数据更新到数据库后,将更新操作发送到消息队列中,然后再由消息队列异步地触发删除缓存数据的操作
  2. 这样做的好处是,即使在更新数据库后发生异常或者网络延迟等问题,数据更新操作也已经被放到消息队列中,并不会导致缓存数据和数据库数据不一致的问题
举报

相关推荐

0 条评论