乐趣区

关于go:gozero微服务实战系列六缓存一致性保证

只有咱们应用缓存,就必然会面对缓存和数据库间的一致性问题。如果缓存中的数据和数据库的数据不统一,那么业务利用从缓存中读取的数据就不是最新的数据,对业务的影响可想而知。比方咱们把商品的库存数据存在缓存中,如果缓存中库存数据不对,那么可能就会影响下单操作,这是业务上很难承受的。本篇文章咱们来一起聊一聊缓存的一致性问题。

如何解决缓存不统一

先删缓存再更新数据库

假如线程 A 删除缓存后,还没来得及更新数据库,这时候线程 B 开始读数据,线程 B 发现缓存缺失就只能去读数据库,等到线程 B 从数据库中读取完数据回塞缓存后,线程 A 才开始更新数据库,此时,缓存中的数据是旧值,而数据库中是最新值,两者曾经不统一了。

这种场景的解决方案是在线程 A 更新完数据库的值后,能够让它 sleep 一小段时间,再进行一次缓存删除操作,之所以要加上 sleep 的一段时间,就是为了让线程 B 可能先从数据库读取出数据而后再把缓存 miss 的数据回塞到缓存,而后线程 A 再进行删除。所以线程 A 的 sleep 工夫就须要大于线程 B 读取数据再写入缓存的工夫。这个工夫是多少呢?这个是须要咱们在业务中退出打点监控来统计的,依据这个统计值来估算该工夫。这样一来,其余线程读取数据时,会发现缓存缺失,就会从数据库中读取最新的值。咱们把这种模型叫做 “ 延时双删 ”。

先更新数据库再删除缓存

如果线程 A 更新了数据库中的值,但还没来得及删除缓存中的值,线程 B 这时候开始读取数据,此时,线程 B 查问缓存时,命中了缓存,就会间接应用缓存中的值,该值为旧值。不过在这种场景下,如果并发申请量不高的话,其实基本上不会有线程读到旧值,而且线程 A 更新完数据库后,删除缓存是十分快的操作,所以,这种状况总体对业务影响较小。个别在生产环境中,也举荐大家采纳该模式。

重试机制

能够把要删除的缓存值或者要更新的数据库的值放到音讯队列中,当利用没可能胜利地删除缓存或者是更新数据库的值的时候,能够从音讯队列中生产这些值,这里生产音讯队列的服务叫 job,而后再次进行删除或者更新,起到一个兜底弥补的作用,以此来保障最终的一致性。

如果可能胜利地删除或更新,就须要把这些值从音讯队列中去除,免得反复操作,此时,咱们也能够保障数据库和缓存数据的统一了,否则的话,咱们还须要再次进行重试,如果重试超过肯定次数还是失败,这时候个别都须要记录谬误日志或者发送告警告诉。

并发读写

首先第一步线程 A 读取缓存,这时候缓存没有命中,因为应用的是 cache aside 这种模式,所以接下来第二步线程 A 会去读数据库,这个时候线程 B 更新数据库,更新完数据库后通过 set cache 更新了缓存,最初第五步线程 A 把从数据库读到的值通过 set cache 也更新了缓存,然而这时候线程 A 中的数据曾经是脏数据了,因为第四步和第五步都是设置缓存,导致写入的值互相笼罩,并且操作的程序具备不确定性,从而导致了缓存不统一状况的产生。

怎么解决这个问题呢?其实十分地简略,咱们只须要把第五步的 set cache 操作替换成 add cache 即可,add cache 即 setnx 操作,只有缓存不存在的时候才会胜利写入,相当于加了优先级,即更新数据库后的更新缓存优先级更高,而读数据库后回塞缓存的优先级较低,从而保障写操作的最新数据不会被读操作的回塞数据笼罩。

结束语

本篇文章阐明了在应用缓存时最常遇见的一个问题,也就是缓存和数据库不统一的问题,针对这个问题咱们列举了一些可能导致不统一的场景以及对应场景的解决方案,特地地,对于 job 异步弥补的场景咱们能够应用 set 操作来强行笼罩缓存,保障缓存的更新为最新的数据,而对于读数据库回塞缓存的操作咱们个别应用 add 来更新缓存。

心愿本篇文章对你有所帮忙,谢谢。

每周一、周四更新

代码仓库: https://github.com/zhoushuguang/lebron

我的项目地址

https://github.com/zeromicro/go-zero

欢送应用 go-zerostar 反对咱们!

微信交换群

关注『微服务实际 』公众号并点击 交换群 获取社区群二维码。

退出移动版