经常看到一个问题redis和mysql数据同步方式有几种,哪种更适用,个人感觉虽然不难,但是有时候绕不明白,所以做了写总结,并且使用图解的方式通俗易懂。

redis和mysql数据同步有五种情况:

1.更新数据库,更新缓存

2.更新缓存,更新数据库

3.删除缓存,更新数据库

4.更新数据库,删除缓存

5.删除缓存,更新数据库,延时删除缓存

1.更新数据库,更新缓存

redis 延迟队列删除队列中的数据 redis延时双删代码实现_redis

图中的场景是有一台mysql和redis数据库初始值都是10,用户1(上面的用户)此时进行了更新操作,把num改成9,此时mysql数据库更新成了9,但是更新缓存之前用户2(下面的用户)进行查询操作,这时读取的是redis的旧值也就是10,mysql和redis数据不一致。总结来说就是步骤2和步骤3插入了一个线程访问,这样就更好理解。

 2.更新缓存,更新数据库

这种情况和第一种原理类似。

3.删除缓存,更新数据库

redis 延迟队列删除队列中的数据 redis延时双删代码实现_redis_02

redis 延迟队列删除队列中的数据 redis延时双删代码实现_redis 延迟队列删除队列中的数据_03

第一幅图用户1去更新数据num=9,此时执行步骤1删除缓存,redis中的10被删除,但是此时用户2执行查询操作由于redis前面删除了所以会去查询mysql,此时的mysql因为还没更新所以用户2得到的值依然为10,并缓存到redis中,最后在执行数据库更新操作,mysql的数据变成了9。mysql和redis数据不一致。

4.更新数据库,删除缓存

redis 延迟队列删除队列中的数据 redis延时双删代码实现_缓存_04

redis 延迟队列删除队列中的数据 redis延时双删代码实现_redis 延迟队列删除队列中的数据_05

和上述类似,用户1请求更新操作num=9,此时执行步骤1,更新数据库,mysql的值变成9, 

但是此时用户2查询操作,发现redis有值返回10,虽然这时候mysql和redis的数据不一致,但是最后执行步骤4,删除缓存之后其他线程访问得到的数据一致,也就是说这个过程是最终一致性,中间只是部分数据不同步,有什么解决办法吗?延时双删可以解决!

 5.删除缓存,更新数据库,延时删除缓存

redis 延迟队列删除队列中的数据 redis延时双删代码实现_数据库_06

redis 延迟队列删除队列中的数据 redis延时双删代码实现_缓存_07

 

redis 延迟队列删除队列中的数据 redis延时双删代码实现_数据库_08

 终于到了延时双删!用户1更新数据num=9,然后执行步骤2删除缓存,此时用户2来查询数据,由于redis缓存被前面一步删除,所以查询mysql数据库得到num=10,并缓存到redis中,然后执行步骤4更新数据库mysql得到num=9,此时延时一段时间再把缓存给删除,下次其他用户访问时就能把mysql中的数据同步到redis实现了数据同步!但是由于延时,造成了性能不佳,吞吐量不高,不适合高并发场景。

为什么要延时呢?

对照第二张图,此时redis没有值,用户2去查询mysql数据库,然后返回值并构建缓存,如果不是延时删除缓存而是直接删除缓存,那么有可能返回值构建缓存还没完成,在这种情况下直接删除缓存,又会把从mysql返回值(旧值)构建缓存,就相当于没有其任何作用。说白了,延时的目的就是让用户2有时间将mysql查询到的值构建到redis中,只有构建好了再删除才能确保最后redis中没有缓存。

附加:

MQ重试机制 

redis 延迟队列删除队列中的数据 redis延时双删代码实现_mysql_09

使用canal

redis 延迟队列删除队列中的数据 redis延时双删代码实现_redis_10

总结:一般redis和mysql数据同步使用先更新数据库,再删缓存,到达最终一致性,延时双删虽然可以保证数据一致性但是由于需要延时因而不适合高并发场景,在高并发场景下可以使用MQ重试机制,如果删除缓存失败就一致重试,但是高耦合。低耦合的解决方案是使用canal。canal伪装成mysql的从机,监听主机mysql的二进制文件,当数据发生变化时发送给MQ。