首页 >> 大全

如何保障数据库和Redis缓存的一致性

2023-11-13 大全 33 作者:考证青年

前言

随着互联网的高速发展,使用互联网产品的人也越来越多,服务不可避免得也会面对越来越复杂的高并发业务场景(如下图),比如热点视频/文章的观看(读场景),热点视频/文章的评论,点赞等(写场景)。

众所周知,数据库是把数据存储在磁盘上,访问时需要进行IO操作,在请求量小的情况下,耗时还比较低。但随着数据量的增大,访问量的集中,整个数据库负担加重,响应就会变慢,请求延时上升。进而导致用户侧的等待时间变长,很大程度上影响了用户体验。

为了解决这一问题,在整个请求链路上就引入了缓存的策略。使用 Redis 用来实现应用和数据库之间读写操作的缓冲。既可以减少数据库 IO,还可以提升数据的 IO 性能。我们只需要将数据库中的数据copy一份到Redis缓存(毕竟可是号称单机抗10w qps)。由于Redis的数据是直接存在内存中,因此读写数据速度提升,整体请求响应速度也得到了提高。

当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到 Redis 里面。

在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和 Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 数据库,由于更新是有先后顺序的,并且它不像 数据库 中的多表事务操作,可以满足 ACID 特性。所以就会出现数据一致性问题。

在高并发的业务场景下,用户的的操作无非就是读场景和写场景。

针对读场景 如果Redis中存在用户所需数据,直接返回即可;如果不存在,则试图从db中读取数据,读取完成后,将该数据更新到Redis中,避免下次再次查询或其他用户查询时,仍从db读取。在高并发场景下,两次请求同时请求Redis,发现数据并不存在后,都会从db中加载数据,然后再重新将数据写入Redis,无论两次请求的写操作谁先谁后,数据本身是一样的,只不过额外进行了一次数据写覆盖。因此不会产生数据不一致的问题。

针对写场景 如果Redis中本身不存在缓存数据,则直接修改db中的数据即可,不会产生数据不一致问题。如果Redis中已存在缓存数据,则需要同时修改db和Redis中的数据,但是二者修改操作的执行必然存在先后顺序。在高并发的场景下,就有可能产生数据不一致的情况。

那么针对此中数据不一致问题,就产生了以下两个疑问:

由于Redis中的数据可有可无,那么当数据发生变化时,是对Redis中的数据进行修改,还是直接删除对应的Redis,然后通过后续的读请求再回源db将数据重新写入Redis呢?Redis和db的数据写操作的顺序问题,是先更新Redis,还是先更新db? 问题1,缓存数据淘汰 or 更新 方案1: 淘汰缓存策略 方案2: 更新缓存策略 综合分析

其实业界一般采用的都是缓存淘汰策略,而非缓存更新策略。原因有三:

大多数情况下,Redis缓存中的数据并不是完全copy db中的数据,而是将db中多张表的数据进行了重新计算,筛选后更新到Redis。如果在db某一张表的数据发生了变化的情况下,需要同步重新计算Redis中的值,成本过高。缓存更新后的新值,无法保证一定会有读请求命中,如果一直没有请求命中该部分冷数据,其实是产生了一定的资源浪费(计算成本+存储成本)。相较于淘汰缓存策略中,仅有一次读请求cache miss的结果来说,淘汰缓存策略的缺点完全可以容忍。

举个例子来说,a表中的字段,1分钟更改了100次,如果采用更新缓存策略,则需要计算100次,哪怕1分钟内只有1次读请求;如果采用淘汰缓存策略,如果1分钟内只有1次请求,则只需要计算1次即可,开销大幅度降低。

问题2,Redis和db写操作,谁先谁后?

注意:以下的方案讨论都是基于Redis淘汰缓存操作以及数据库更新操作保证成功(可通过重试机制解决)的情况下,高并发的业务场景中的解决方案。

方案1: 先淘汰缓存,后更新数据库

正常情况

异常情况1

对于该种异常情况,提供两种解决思路:

异步更新缓存。

(1)、A请求进行写操作,先淘汰缓存

(2)、B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在Redis中发现所需数据,因此从数据库中读取数据。注意,此时不向Redis写入新的缓存策略

(3)、A请求通过订阅数据库,对Redis缓存数据进行异步更新

(4)、该方案虽然解决了数据不一致的问题,但是在数据库更新操作完成前,所有的读请求都会直接打到数据库上,具有比较大的风险。延时双删。

(1)、A请求进行写操作,先淘汰缓存

缓存数据一致性方案_缓存数据一致性解决方案_

(2)、B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在Redis中发现所需数据,因此从数据库中读取数据,并更新缓存到Redis中。注意,此时Redis中被更新的依然是老数据,A请求的数据库更新操作尚未完成。假设该步骤耗时N秒

(3)、A请求进行数据库更新操作。

(4)、由于此时Redis中写入了老数据,因此A请求在休眠M秒后(M略大于N),再次对Redis进行淘汰缓存操作

(5)、该方案虽然解决了数据不一致的问题,但是由于请求A在更新完数据库之后,需要休眠M秒再次淘汰缓存,一定程度上影响了数据更新操作的吞吐量。可以尝试将等待M秒更新Redis的操作放到另一个单独的线程(比如消息队列 + 重试机制)。可以有效缓解吞吐量降低的问题。

异常情况2

方案2: 先更新数据库,后更新淘汰缓存

正常情况

异常情况1

异常情况2

总结

技术是为业务服务的,所以不同的业务场景,对于技术的选择和方案的设计都是不同的,我们一定要知道的是,一个技术方案不可能 cover 住所有的场景。

其实数据库和缓存的一致性问题,乍一看,情况比较复杂,但是通过分析业务使用场景,以及正常和异常的请求情况,是可以抽丝剥茧,逐步解决的。

在此只列举出了部分异常情况以及针对这些异常情况提出的一些解决方案,其实在这个过程中,可以讨论和研究的点还有很多:

特别鸣谢:面试官:如何保障数据库和Redis缓存的一致性

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了