引言
如何保证redis与db的双写一致性?这是一个十分热门的面试话题。 如何理解“一致性”这个概念?“事务”中“一致性”的定义是: 事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。 比喻说,更新前:redis中记录的是100,db中记录的也是100。更新后: redis中记录的是 80,db中记录的也是 80。
一、问题场景假设
在同时更新redis和db时,可能出现更新某一个失败或者更新不及时,导致双写一致性问题。
比喻说,更新前:redis中记录的是100,db中记录的也是100。
二、基本原则
1、数据必须优先落库db。redis只是db的数据缓存。
2、做更新操作时,数据必须以db的为主。不能以redis查询的数据,去做计算再更新db。
3. 更新db时,为了保障隔离性。需要加锁(基于版本号的乐观锁或者for update悲观锁)
4. 查询时先查询redis,如果redis未命中,则继续查db。再把查询结果写回redis
三、可能的解决办法
1.先更新缓存,再更新数据库
问题一:更新redis成功+更新db失败。
案例更新结果: redis中记录的是 80,db中记录的也是 100
(不可取。除了没有保障一致性,而且违背了原则1)
2.先更新数据库,再新缓存
问题一:更新db成功+更新redis失败
案例更新结果: redis中记录的是 100,db中记录的也是 80。
问题二:更新db成功+更新redis不及时
正常情况:
- 线程A更新db为80
- 线程A更新redis为80
- 线程B更新db为90
- 线程B更新redis为90
异常情况1
- 线程A更新db为80
- 线程B更新db为90
- 线程B更新redis为90
- 线程A更新redis为80
案例更新结果: redis中记录的是 90,db中记录的也是 80。
3.先删除缓存,再更新数据库
问题一:删除redis成功+更新db延迟
异常情况2
- 线程A删除redis缓存
- 线程A更新DB为80。执行完需要5秒
- 线程B在A更新DB第1秒开始执行查询。查询redis没命中,再查询db为100,再更新redis为100
案例更新结果: redis中记录的是 100,db中记录的也是 80。
4.延迟双删(先删除redis,再更新db,再删除redis)
延迟双删,可以解决异常情况2。
异常情况3:
- 线程A删除redis缓存成功
- 线程A更新db为80。执行完需要5秒
- 线程B在A更新DB第1秒开始执行查询。查询redis没命中,再查询db为100,再更新redis为100
- 线程A删除redis失败。redis宕机了。
(为了解决第二次删除redis可能失败,可以使用消息队列或者canel订阅mysql的binlog日志等实现延迟删除 + 失败补偿)
5.先更新数据库,再删除缓存
问题一:更新db成功+删除redis失败
异常情况
- 线程A更新DB为80
- 线程A删除redis失败
- 线程B执行查询。先查询redis,命中返回结果100
案例更新结果: redis中记录的是 100,db中记录的也是 80。
四、推荐解决方案
技术选型:
mysql + rabbitmq + canel + redis
1.redis采用集群,实现高可用。
2.查询。先查询redis。未命中,则DCL(双检锁) 的方式,查询db。查询结果再写入redis。
3.更新。
3.1 更新db时,需要加锁。如加for update悲观锁,或者基于版本号的乐观锁。(防止多线程同时更新,相互影响)
3.2 更新db时,set的字段取值,不能使用redis查询的结果,必须使用db的查询结果(避免读到redis的脏数据,而db如mysql的默认隔离级别是可重复读,不会读到脏数据)
3.3 更新顺序。必须优先保障数据落库db。可以分为:
3.3.1 延迟双删(先删除redis, 再更新db,最后再删除redis)。
在落地时,为了防止第二次删除redis失败,可以通过以下两种方案:
3.3.1.1 基于消息队列
在准备第二次删除redis缓存时,将其放入消息队列。消息队列消费失败,放入死信队列。预设死信队列失败重试次数。死信队列消费次数超过阈值,则日志报警。(消息队列也要搭建集群来保障可用性)
3.3.1.2 canel订阅mysql的binlog日志
通过阿里的canel中间件来订阅mysql的binlog日志,来实现异步删除。删除失败,则放入消息队列。
3.3.2 先更新db,再删除redis。
先删除db,然后删除redis。如果删除redis失败,则放入消息队列。消息队列消费失败,则放入私信队列。文章来源:https://www.toymoban.com/news/detail-730972.html
文章来源地址https://www.toymoban.com/news/detail-730972.html
到了这里,关于如何保证redis与db的双写一致性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!