Redis缓存一致性灵魂4连问
Redis缓存一致性灵魂4连问
Mr.Yun前言
如果我们使用 Redis 做MySQL等数据库的缓存层,就必然会面对缓存和数据库间的一致性保证问题,这也算是 Redis 缓存应用中的“必答题”了。
部分面试官对缓存一致性的题目青睐有加。多数人在面试时就亲身经历过面试官连环题目的“严刑拷打”,直至今日还记得当时无言以对的尴尬,最终只得以面试官留下一句“好吧”来收场。
为了充分考察候选人的能力,面试官设计了直击灵魂的连环4问,无死角、全方位考察你对问题的理解。下面让我们逐一剖析这些问题,并构想如何以一种条理清晰、逻辑严密的方式应答,以展现你的技术深度与广度。
问题1:Redis常用作MySQL等数据库的缓存层,如何保证Redis缓存和数据库数据的一致性?
听到这个问题,你心里一阵窃喜,这个研究过,网上的文章也给过答案。
于是告诉面试官方案如下:
1、写操作时:先更新数据库,再清除缓存;
2、读操作:读取缓存,存在则直接返回,不存在则读取数据库,之后更新到缓存。
如下图所示:
问题2:为什么是删除缓存,而不是更新缓存呢?
你从容作答:
更新缓存会有并发问题,可能会导致缓存与数据库数据不一致,这对大多数业务场景来说是不能接受的。如线程1和2都是写操作,线程1先完成数据库写操作,然后线程2完成了数据库和缓存的写操作,之后线程1完成缓存写操作,那么此时缓存和数据库的数据就不一致了。
如下图所示:
问题3:为什么不是先删除缓存,再更新数据库呢?
先删除缓存、再更新数据库容易造成读写请求并发问题,可能造成数据不一致。另外,先删除缓存,由于缓存中数据缺失,加剧数据库的请求压力,可能会增大缓存穿透出现的概率。
数据不一致的场景是:线程1删除缓存后,线程2读取到数据库旧值,之后更新旧值到缓存中,之后线程1更新数据库,造成缓存不一致。如下图所示:
因为写数据库往往慢于读请求,所以此问题出现的概率还是相对较大的。
如果使用此方案,业界也提出了“延迟双删”的方案解决不一致问题,即在更新数据库后,再操作一次删除缓存。为了保证第二次删除缓存的时间点在读请求更新缓存之后,这个延迟时间的经验值通常应稍大于业务中读请求的耗时。延迟的实现可以在代码中 sleep
或采用延迟队列。显而易见的是:
无论这个值如何预估,都很难和读请求的完成时间点准确衔接,这也是延迟双删被诟病的主要原因。
延迟双删的流程如下图所示:
问题4:那当前这个方案在并发读写的时候会有数据不一致的问题吗?
当前方案出现数据不一致问题的概率很小,出现的条件极其严苛。如果需要强一致性,也是需要加分布式锁的,但是这样的话,方案的复杂性就会大大增加了。
类比上述读写并发的场景,线程1读请求,此时缓存刚好失效了,就从数据库中读取了旧值,然后线程2更新数据库并操作清除了缓存,之后线程1更新旧值到缓存中。如下图所示:
由上图可知,出现数据不一致问题的条件包括:
(1)线程1读数据时,无缓存;
(2)线程1读请求、线程2写请求并发;
(3)线程1更新缓存比线程2更新数据库+删除缓存加起来耗时都长。
可见,出现该问题的条件还是比较苛刻的,尤其是第(3)个条件,一般情况下更新数据库都是比更新缓存要慢的,除非刚好线程1到缓存服务刚好出现网络抖动,才会出现该问题。
还有一点,需要指出:上述谈到的数据不一致都是缓存与数据库中的数据可能由于并发等问题导致的长时间不一致,避免该问题,即达成了缓存与数据库数据的最终一致性。
如果需要缓存与数据库数据的强一致性,必然要把操作数据库和缓存放置到同一事务中,操作资源时也需要加分布式锁避免并发读写造成的不一致,这也会导致方案复杂度的上升和请求性能的下降。
总结
面对这一系列围绕Redis缓存与数据库一致性挑战的深入探讨,面试官步步紧逼的背后,实则是对候选人系统思维、问题解决策略及实际经验的深度挖掘。
当你以为自己已经过关斩将成功时,下一个问题可能直接让你手足无措,比如以下这些问题:
(1)更新数据库成功、删除缓存失败了,怎么处理?
(2)以上方案适用于什么场景呢?适合秒杀场景使用么?
(3)缓存应用还有哪些策略?分别适用于什么场景呢?
(4)生产环境中MySQL等数据库一般都会使用主备模式,应用程序会写主库读从库。如果采用 写数据库清缓存+读数据库更新缓存 的方案,读请求可能因为从库没同步最新的数据而更新旧数据到缓存中,造成缓存不一致,这种情况如何解决?
参考:
[1] https://cloud.tencent.com/developer/article/1932934
[2] https://zhuanlan.zhihu.com/p/479771075
[3] https://www.jianshu.com/p/37b2f60add41
[4] https://xiaolincoding.com/redis/architecture/mysql_redis_consistency.html