Redis面经——Redis的双写一致性,一篇文章带你彻底搞懂什么是Redis的双写一致,如何推演实现的?以及成熟的解决方案!黄金文档!
一、双写一致性概念
当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致
Redis的双写一致性,一般是基于两种场景,第一个是追求强一致性,第二个允许延迟一致(保证数据的最终一致性)
二、保证双写一致性细节
追求强一致性
(一)读操作
缓存命中,直接返回;缓存没有命中则查询数据库,写入缓存,设定超时时间
(二)写操作
延迟双删
那么问题来了,是先删除缓存呢还是先修改数据库呢?
1、先删除缓存,再操作数据库
假设最开始缓存和数据库中的数据都是10条
有两个线程
(1)先删除缓存
(2)更新数据库数据
(3)查询缓存,缓存未命中,则查询数据库,将数据写入到缓存中
但是这种方式还是会存在问题的!
请继续往下看
(1)线程1此时删除了缓存,由于线程的调度完全是由CPU来控制,此时线程1挂起,而线程2开始查询数据,此时线程2肯定是未命中的状态,就去查询数据库,并将数据库更新前的数据又重新写入到缓存了
(2)此时,线程1开始执行,线程1完成了数据库的数据更新,数据库数据变成了20,但是此时的缓存中数据还是10,这就造成了脏数据的情况!!!
2、先操作数据库,再删除缓存
1)正常的流程
假设初始缓存和数据库中的数据都是10
线程2开启,并直接更新了数据库,然后线程2删除了缓存。此时线程1查询缓存,发现缓存未命中,直接去查询数据库,此时查询的数据是正确的,然后紧接着将数据库更新后的数据写到缓存中了。到此为止,数据还是一致性的
2)存在的问题
(1)如果此时缓存中的数据过期了,那么线程1是拿不到数据的,直接查询数据库拿到一开始的10
(2)接下来线程2在线程1未同步到缓存前,先更新数据库,并且删除了缓存(此时缓存中key已经过期,删不删除都是一样的),线程2成功将数据更新成了20,但是线程1开始执行,将一开始读取到的10写入到了缓存中,又总成了数据的不一致!
我们继续回到上面的问题
写操作,要延迟双删
那么又有一个新的问题,就是为什么要删除两次缓存呢?
因为再次删除缓存,目的就是为了降低脏数据的风险
还又一个新的问题,为什么要延时删除呢?
因为数据库一般都是搭建主从集群的,所以要等待数据库的数据全部同步后开始删除缓存,否则还是会出现脏数据的情况。注意,这种方式没有绝对的一致性,延时多久不可预知,如果出现主从未完全同步时写入缓存,还是会导致脏数据
保证写操作强一致性的方法!!!!重头戏!!!!!!
1、加入分布式锁,来控制
2、进阶版
因为一般写入到缓存中的数据都是读多写少的情况,这个时候我们就不用分布式锁了,直接使用读写锁!
具体的代码实现:
下面的读写代码,一定可以实现强一致性!但是性能就很低了!
(1)读操作:
(2)写操作:
注意读写锁的锁要保持一致
允许延迟一致(保证数据的最终一致性)
1、异步通知来保证数据的最终一致性
2、基于Canal的异步通知
canal是基于MySQL的主从同步来实现的
会监听主节点的binlog文件,binlog二进制文件中记录了所有的DDLH和DML语句,但是不包括数据查询,比如select或者show语句。
当文件发生更新了,那么就会通知缓存数据变更的情况。这种方式是对业务代码0侵入的,如果我们的业务场景中允许数据短暂延时,这种方式还是相对推荐的
至此,关于双写一致性的推演介绍完毕,内容比较多,希望大家能够收藏反复学习,后续还会持续更新相关面试题,敬请期待!!!