当前位置:   article > 正文

Redis面试题分享十:Redis 与 MySQL如何保证双写一致性?

Redis面试题分享十:Redis 与 MySQL如何保证双写一致性?

目录

一、什么是数据一致性问题

二、四种读写缓存策略

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

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

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

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

三、解决问题策略思想

四、具体解决方案

1、延时双删

2、Binlog实现异步重试删除


一、什么是数据一致性问题

在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 Mysql,由于更新是有先后顺序的,并且它不像 Mysql 中的多表事务操作,可以满足 ACID 特性。所以就会出现数据一致性问题。

Redis与MySQL双写一致性是指在使用缓存和数据库同时存储数据的场景下( 主要是存在高并发的情况)如何保证两者的数据一致性(内容相同或者尽可能接近)

二、四种读写缓存策略

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

线程A:更新缓存(第1s)——> 更新数据库(第10s)

线程B:更新缓存(第3s)——> 更新数据库(第5s)

缓存中是线程B的新值,而数据库中是线程A的旧值。

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

线程A:更新数据库(第1s)——> 更新缓存(第10s)

线程B:更新数据库 (第3s)——> 更新缓存(第5s)

并发场景下,这样的情况是很容易出现的,每个线程的操作先后顺序不同,这样就导致请求B的缓存值被请求A给覆盖了, 数据库中是线程B的新值,缓存中是线程A的旧值,并且会一直这么脏下去直到缓存失效(如果你设置了过期时间的话)。

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

通过这种方式,我们很惊喜地发现,前面困扰我们的并发场景的问题确实被解决了!两个线程都只修改数据库,不管谁先,数据库以之后修改的线程为准。

但这个时候,我们来思考另一个场景:两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的。很显然,这种状况也不是我们想要的。

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

这种方式,在方案3的基础上,又将二者的顺序进行了调换。我们再把前面的场景在这种方案下进行验证:一个是查询操作,一个是更新操作的并发,我们先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会方案3一样,后续的查询操作一直在取老的数据。

而这,也正是缓存使用的标准的design pattern,也就是cache aside。包括Facebook的论文《Scaling Memcache at Facebook》也使用了这个策略。

那么,是否这种方案就是万无一失的完美策略呢?其实也并不然,再来看看这种场景:一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。

因此,在使用更新缓存操作时,无论谁先谁后,但凡后者发生异常,就会对业务造成影响。

三、解决问题策略思想

  1. 选择合适的数据同步方式:根据应用的需求和数据访问模式,选择合适的数据同步方式。同步方式包括单向同步、双向同步和不同步等。
  2. 使用事务:在 MySQL 中使用事务来确保数据的原子性。在 Redis 中,可以使用 MULTI/EXEC 命令组来执行事务操作。
  3. 避免长时间的事务:MySQL 中的长时间事务可能会导致 Redis 中的数据不同步。因此,在设计数据库操作时,应尽量避免长时间的事务。
  4. 主从复制:使用 MySQL 的主从复制功能,将数据变更同步到从数据库。然后,使用 Redis 的订阅/发布功能,将从数据库中的数据变更同步到 Redis。这种方式可以实现 MySQL 和 Redis 的双向同步。
  5. 分布式事务:使用分布式事务协议,如两阶段提交或三阶段提交,来确保 Redis 和 MySQL 之间的数据一致性。分布式事务可以保证多个数据库之间的数据一致性。
  6. 定时同步:对于一些不需要实时同步的数据,可以使用定时同步的方式。例如,每天定时将 MySQL 中的数据同步到 Redis 中。
  7. 数据校验:定期进行数据校验,以确保 Redis 和 MySQL 中的数据一致。如果数据不一致,及时进行修复。

四、具体解决方案

1、延时双删

我们重点在将先更新数据库,在删除缓存。那如果我要先删除缓存,再更新数据库呢?
回顾之前讲的先删除缓存,再更新数据库,它会出现旧值覆盖缓存的问题,那好办,我们直接把这个旧值给删了不就完了吗,延时双删就是这个原理,它的基本思路是:

  • 先删除缓存
  • 再更新数据库
  • 休眠一段时间(根据系统情况确定)
  • 再次删除缓存

这样做的目的是为了防止在更新数据库后,有其他线程读取到旧的缓存数据,并将其写回缓存,导致数据不一致。

2、Binlog实现异步重试删除

使用binlog实现一致性的基本思路是利用binlog日志来记录数据库的变更操作,然后通过主从复制或者增量备份的方式来同步或者恢复数据。

举例来说,如果我们有一个主数据库和一个从数据库,我们可以在主数据库上开启binlog日志,并设置从数据库作为它的复制节点。这样,当主数据库上发生任何变更操作时,它会将对应的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。

另外,如果我们需要恢复某个时间点之前的数据,我们也可以利用binlog日志来实现。首先,我们需要找到对应时间点之前的最近一个全量备份文件,并将其恢复到目标数据库。然后,我们需要找到对应时间点之前的所有增量备份文件(即binlog日志文件),并按照顺序将其应用到目标数据库。这样,我们就可以恢复出目标时间点之前的数据状态了。

  • 使用 Binlog 实时更新/删除 Redis 缓存。利用 Canal,即将负责更新缓存的服务伪装成一个 MySQL 的从节点,从 MySQL 接收 Binlog,解析 Binlog 之后,得到实时的数据变更信息,然后根据变更信息去更新/删除 Redis 缓存;
  • MQ+Canal 策略,将 Canal Server 接收到的 Binlog 数据直接投递到 MQ 进行解耦,使用 MQ 异步消费 Binlog 日志,以此进行数据同步;RocketMQ实现最终一致性;比如基于 RocketMQ 的可靠性消息通信,来实现最终一致性;Canal 组件还可以直接通过 Canal 组件,监控 Mysql 中 binlog 的日志,把更新后的数据同步到 Redis 里面。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/165155
推荐阅读
相关标签
  

闽ICP备14008679号

        
cppcmd=keepalive&