Redis作为缓存,mysql的数据如何与redis进行同步?
一定要设置前提,先介绍业务背景
延时双删
- 双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致
- 读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间
- 写操作:延时双删
问题来了,那么先删除数据库,还是先删除缓存呢?我们都分析一下,这里只分析异常情况
-
先删除缓存,再删除数据库。假设我们有缓存数据A=10,和数据库数据A=10
- 线程1先删除缓存
- 此时线程2查询缓存,未命中,查询数据库A=10,然后写入缓存
- 线程1然后更新数据库A=20
- 此时数据不一致了缓存A=10,数据库A=20
-
先操作数据库,再删除缓存,假设我们有缓存数据已经过期,和数据库数据A=10
- 线程1先查询缓存,此时因为我们假设的缓存数据已经过期,查询数据库A=10,还没来得及写入缓存
- 线程2更新数据库,A=20,然后删除缓存(此时没有缓存,删不删没关系)
- 线程1写入缓存
- 此时数据不一致了,缓存A=10,数据库A=20
我们发现这两种都会导致脏数据的出现,所以,删除两次缓存就出现了。这就是延时双删:删除缓存-修改数据库-延迟一会删除缓存
,就是为了降低脏数据的出现,那为什么要延时呢?因为一般情况下,数据库是主从模式,我们要延时一会,让数据库主节点同步到从节点,再删除缓存。但是也会有小问题,因为延时的时间不好把控。所以做不到绝对的强一致。
分布式锁
那有更好的办法吗?我们可以使用分布式锁来解决这个问题
但是这个显然效率有点太低了,我们可以优化一下
一般缓存数据都是读多写少,我们可以使用读写锁
控制
- 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作排他锁
- 独占锁writeLock也叫,加锁之后,阻塞其他线程读写操作
这样性能就得到了提升,虽然实现了强一致,但是性能还是有点低
异步通知保证数据最终一致
最终一致性的保证,主要取决于MQ的可靠性
基于Canal的异步通知
canal是阿里开发的中间件,主要是基于mysql的主从同步实现的
二进制日志(BNLOG)记录了所有的DDL (数据定义语言)语句和DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
优点:无代码侵入
总结
- 允许延时一致的业务,采用异步通知
- 使用MQ中间中间件,更新数据之后,通知缓存删除
- 利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓
- 存强一致性的,采用Redisson提供的读写锁
- 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作排他锁
- 存强一致性的,采用Redisson提供的读写锁
- 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作排他锁
- 独占锁writeLock也叫,加锁之后,阻塞其他线程读写操作