Redis与MySQL数据双写一致性问题

Canal
介绍:

canal [kə’næl],中文翻译为 水道/管道/沟渠/运河,主要用途是用于 MySQL 数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发;
历史背景是早期阿里巴巴因为杭州和美国双机房部署,存在跨机房数据同步的业务需求,实现方式主要是基于业务 trigger(触发器) 获取增量变更。从2010年开始,阿里巴巴逐步尝试采用解析数据库日志获取增量变更进行同步,由此衍生出了canal项目;

作用:

  1. 数据库镜像。
  2. 数据库实时备份。
  3. 索引构建和实时维护(拆分异构索引,倒排索引等)。
  4. 业务cache(缓存)刷新。
  5. 带业务逻辑的增量数据处理。

工作原理:
传统MySQL主从复制工作原理:
Redis与MySQL数据双写一致性问题_第1张图片
MySQL的主从复制将经过如下步骤:

  1. 当master主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中。
  2. salve从服务器会在一定时间间隔内对master主服务器上的二进制日志进行探测,探测其是否发生过改变,如果探测到master主服务器的二进制事件日志发生了改变,则开始一个I/O
    Thread请求master二进制事件日志。
  3. 同时master主服务器为每个I/O Thread启动一个dump Thread,用于向其发送二进制事件日志。
  4. slave从服务器将接收到的二进制事件日志保存到自己本地的中继日志文件中。
  5. slave从服务器启动SQL Thread从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致。
  6. 最后I/O Thread和SQL Thread将进入睡眠状态,等待下一次被唤醒。

canal工作原理:

  1. canal模拟MySQL slave的交互协议,伪装自己为MySQL slave,向MySQL master发送dump请求。
  2. MySQL master收到dump请求,开始推送binary log给slave(即canal)。
  3. canal解析binary log对象(原始为byte流)。

Redis与MySQL数据双写一致性问题_第2张图片

缓存双写一致性之更新策略探讨
缓存双写一致性

  • 如何Redis中有数据,需要和数据库中的值相同。
  • 如果Redis中无数据,数据库中的值要是最新值。

数据库和缓存一致性的机制更新策略:
方法一: 先更新数据库,再更新缓存
异常问题:

  1. 更新MySQL的某商品的库存,当前商品的库存是100,更新为99。
  2. 先更新MySQL为99,然后更新Redis。
  3. 此时假设出现异常,更新Redis失败,这导致MySQL里面的库存是99而Redis里面的库存还是100。
  4. 上述情况发生,会让数据库里面和缓存Redis里面的数据不一致,读到脏数据。

方法二: 先删除缓存,再更新数据库
异常问题:

  1. A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时)B突然出现要来读取缓存数据。
  2. A线程已删除redis数据,此时redis里面的数据是空的,B线程来读取,先去读redis里数据(已经被A线程delete掉了),此处出来2个问题:① B从mysql获得了旧值 B线程发现redis里没有(缓存缺失)马上去mysql里面读取,从数据库里面读取来的是旧值。② B会把获得的旧值写回redis 获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能又被写回了)。
  3. 如果数据库更新失败,导致B线程请求再次访问缓存时,发现redis里面没数据,缓存缺失,再去读取mysql时,从数据库中读取到旧值。

处理方案:
1️⃣ 添加互斥锁

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。
其他的线程走到这一步拿不到锁就堵塞等待,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

2️⃣ 采用延迟双删策略
Redis与MySQL数据双写一致性问题_第3张图片
延迟双删方案面试题
这个删除该休眠多久呢?

线程A sleep的时间,需要大于线程B读取数据再写入缓存的时间。
这个数据怎么确定呢?
在业务程序运行的时候,统计下线程读取数据和写缓存的操作时间,自行评估自己项目的读数据业务逻辑的耗时,以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百ms即可。
这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

如何MySQL是主从读写分离架构该如何处理?

  1. 请求A进行写操作,删除缓存。
  2. 请求A将数据写入数据库。
  3. 请求B查询缓存发现,缓存没有值。
  4. 请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值。
  5. 请求B将旧值写入缓存。
  6. 数据库完成主从同步,从库变为新值。

上述情形,就是数据不一致的原因,还是使用延时双删策略。只是,睡眠时间修改为在主从同步的延时时间基础上加几百ms。

延迟双删策略,吞吐量降低怎么办?

第二次删除异步删除

Redis与MySQL数据双写一致性问题_第4张图片
方案三:先更新数据库,再删除缓存
异常问题:

假如缓存删除失败或者来不及,导致请求再次访问Redis时缓存命中,读取到的是缓存旧值。

处理方案:

  1. 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。
  2. 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  3. 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试。
  4. 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。

Redis与MySQL数据双写一致性问题_第5张图片
总结:

在大多数业务场景下,我们会把Redis作为只读缓存使用。假如定位是只读缓存来说,理论上我们既可以先删除缓存再更新数据库,也可以先更新数据库再删除缓存,但是没有完美方案,两害相衡趋其轻的原则:
个人建议是,优先使用先更新数据库,再删除缓存的方案。理由如下:

  1. 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力,严重导致请求打满MySQL。
  2. 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删策略的等待时间就不好设置。

使用先更新数据库,再删除缓存的方案
如果业务层要求必须读取一致性的数据,那么我们就需要在更新数据库时,先在Redis缓存客户端暂停并发读请求,等待数据库更新完,缓存值删除后,再读取数据,从而保证数据一致性。

Redis与MySQL数据双写一致性问题_第6张图片

你可能感兴趣的:(mysql,Redis,java,mysql,redis,数据库)