如何保证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
案例更新结果: redis中记录的是 80,db中记录的也是 100
(不可取。除了没有保障一致性,而且违背了原则1)
案例更新结果: redis中记录的是 100,db中记录的也是 80。
正常情况:
异常情况1
案例更新结果: redis中记录的是 90,db中记录的也是 80。
异常情况2
案例更新结果: redis中记录的是 100,db中记录的也是 80。
延迟双删,可以解决异常情况2。
异常情况3:
(为了解决第二次删除redis可能失败,可以使用消息队列或者canel订阅mysql的binlog日志等实现延迟删除 + 失败补偿)
问题一:更新db成功+删除redis失败
异常情况
案例更新结果: 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失败,则放入消息队列。消息队列消费失败,则放入私信队列。