写点什么

图解 Redis:一套方案轻松搞定数据库与缓存数据不一致问题

用户头像
极客good
关注
发布于: 刚刚

举个例子,比如「请求 A 」和「请求 B 」两个请求,同时更新「同一条」数据,则可能出现这样的顺序:


![](https://img-blog.csdnimg.cn/img_convert/5f81d9678209b6bd087ecfc1e6745292.webp?x-oss-process=


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


image/format,png)


A 请求先将数据库的数据更新为 1,然后在更新缓存前,请求 B 将数据库的数据更新为 2,紧接着也把缓存更新为 2,然后 A 请求更新缓存为 1。


此时,数据库中的数据是 2,而缓存中的数据却是 1,出现了缓存和数据库中的数据不一致的现象


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


============


那换成「先更新缓存,再更新数据库」这个方案,还会有问题吗?


依然还是存在并发的问题,分析思路也是一样。


假设「请求 A 」和「请求 B 」两个请求,同时更新「同一条」数据,则可能出现这样的顺序:



A 请求先将缓存的数据更新为 1,然后在更新数据库前,B 请求来了, 将缓存的数据更新为 2,紧接着把数据库更新为 2,然后 A 请求将数据库的数据更新为 1。


此时,数据库中的数据是 1,而缓存中的数据却是 2,出现了缓存和数据库中的数据不一致的现象


所以,无论是「先更新数据库,再更新缓存」,还是「先更新缓存,再更新数据库」,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库中的数据不一致的现象


2


阿旺定位出问题后,思考了一番后,决定在更新数据时,不更新缓存,而是删除缓存中的数据。然后,到读取数据时,发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中。


阿旺想的这个策略是有名字的,是叫 Cache Aside 策略,中文是叫旁路缓存策略。


该策略又可以细分为「读策略」和「写策略」。



写策略的步骤:


  • 更新数据库中的数据;

  • 删除缓存中的数据。


读策略的步骤:


  • 如果读取的数据命中了缓存,则直接返回数据;

  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。


阿旺在想到「写策略」的时候,又陷入更深层次的思考,到底该选择哪种顺序呢?


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

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


阿旺这次经过上次教训,不再「想当然」的乱选方案,因为老板这次给的饼很大啊,必须把握住。


于是阿旺用并发的角度来分析,看看这两种方案哪个可以保证数据库与缓存的数据一致性。


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


============


阿旺还是以用户表的场景来分析。


假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21。



最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库的数据不一致。


可以看到,先删除缓存,再更新数据库,在「读 + 写」并发的时候,还是会出现缓存和数据库的数据不一致的问题


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


============


继续用「读 + 写」请求的并发的场景来分析。


假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中。



最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。


从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高


因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。


而一旦请求 A 早于请求 B 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。


所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的


而且阿旺为了确保万无一失,还给缓存数据加上了「过期时间」,就算在这期间存在缓存数据不一致,有过期时间来兜底,这样也能达到最终一致。


阿旺思考到这一步后,觉得自己真的是个小天才,因为他竟然想到了个「天衣无缝」的方案,他二话不说就采用了这个方案,又经过几天的折腾,终于完成了。


他自信满满的向老板汇报,已经解决了上次客户的投诉的问题了。老板觉得阿旺这小伙子不错,这么快就解决问题了,然后让阿旺在观察几天。


事情哪有这么顺利呢?结果又没过多久,老板又收到客户的投诉了,说自己明明更新了数据,但是数据要过一段时间才生效,客户接受不了。


老板面无表情的找上阿旺,让阿旺尽快查出问题。


阿旺得知又有 Bug 就更慌了,立马就登录服务器去排查问题,查看日志后得知了原因。


「先更新数据库, 再删除缓存」其实是两个操作,前面的所有分析都是建立在这两个操作都能同时执行成功,而这次客户投诉的问题就在于,在删除缓存(第二个操作)的时候失败了,导致缓存中的数据是旧值


好在之前给缓存加上了过期时间,所以才会出现客户说的过一段时间才更新生效的现象,假设如果没有这个过期时间的兜底,那后续的请求读到的就会一直是缓存中的旧数据,这样问题就更大了。


所以新的问题来了,如何保证「先更新数据库 ,再删除缓存」这两个操作能执行成功?


阿旺分析出问题后,慌慌张张的向老板汇报了问题。


老板知道事情后,又给了阿旺几天来解决这个问题,画饼的事情这次没有再提了。


阿旺会用什么方式来解决这个问题呢?


老板画的饼事情,能否兑现给阿旺呢?


预知后事,且听下回阿旺的故事。



别问为什么,故事还要分上下回,因为小林昨晚熬夜写,没写完,就先放上回的故事,哈哈哈。


3


阿旺的事情就聊到这,我们继续说点其他。

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
图解Redis:一套方案轻松搞定数据库与缓存数据不一致问题