当前位置: 首页 > news >正文

Redis与MySQL如何保证数据一致性

Redis与MySQL如何保证数据一致性

简单来说

该场景主要发生在读写并发进行时,才会发生数据不一致。

主要流程就是要么先操作缓存,要么先操作Redis,操作也分修改和删除。

一般修改要执行一系列业务代码,所以一般直接删除成本较低。

如果我们先删除Redis中数据,就会出现修改数据库数据时,其他线程读取到Redis为空,从数据库查,并写入Redis中,但这就是脏数据了,所以可以采用延迟双删策略,就是再删除一次,需要延迟的原因是因为在其他线程查时给Redis设置旧数据时,不延迟,就会覆盖新数据。(所以一般都要根据自己的业务逻辑评估一下大概时间。)如果说需要在这个阶段也需要保证数据一致性,那就只能上锁,保证强一致性,因为Redis和数据库是两个服务,只能通过加锁保证原子性,而这就影响了系统吞吐量(就违背了使用Redis提高吞吐量,也就是AP和CP不能同时满足的问题),所以一般都是保证最终一致性。

如果先修改数据库,同样会有数据不一致情况,在修改数据库处理时,其他线程也会读取旧数据,处理完了数据库就会删除缓存,保证了数据最终一致性,(所以比较推荐这种操作)

但是上述两种都会有删除Redis失败的情况,可以选择异步发送消息到MQ,线程监听MQ执行重试删除。

但这里的MQ代码过于耦合,需要解耦的话,就可以将他们提取出来,使用阿里的一款开源框架canal主要用途是基于MySQL数据库增量日志解析,提供增量数据订阅和消费Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端,过程也就是执行失败后,异步发送消息到MQ,Canal客户端监听到MQ消息执行重试

 

详细来说

1、引入

Redis 用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库 IO,还可以提升数据的 IO 性能。

当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到 Redis 里面。

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

e27e36563ec144118801ddf5724ebd46.png

 

2、同步策略

想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:

先更新缓存,再更新数据库; 先更新数据库,再更新缓存; 先删除缓存,再更新数据库; 先更新数据库,再删除缓存。 从这4种同步策略中,我们需要作出比较的是:

更新缓存与删除缓存哪种方式更合适?应该先操作数据库还是先操作缓存?

一般直接删除,因为缓存的更新成本更高 (因为你写入数据库的值,很多情况并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值无疑是浪费性能的。显然,删除缓存更为适合。)删除缓存操作简单,副作用只是增加了一次 chache miss,建议大家使用该策略。

一般先操作数据库,因为更方便保证最终一致性,先往下看

 

3、操作理解

先操作缓存

3f428d8e9f084cea8af330909520c273.png

如果我们先删除Redis中数据,就会出现修改数据库数据时,其他线程读取到Redis为空,从数据库查,并写入Redis中,但这就是脏数据了,所以可以采用延迟双删策略,就是再删除一次,需要延迟的原因是因为在其他线程查时给Redis设置旧数据时,不延迟,就会覆盖新数据。(所以一般都要根据自己的业务逻辑评估一下大概时间。)如果说需要在这个阶段也需要保证数据一致性,那就只能上锁,保证强一致性,因为Redis和数据库是两个服务,只能通过加锁保证原子性,而这就影响了系统吞吐量(就违背了使用Redis提高吞吐量,也就是AP和CP不能同时满足的问题),所以一般都是保证最终一致性。

 

先操作数据库

2ed9f84a85764e61999bf3cdb820b72a.png

 

如果先修改数据库,同样会有数据不一致情况,在修改数据库处理时,其他线程也会读取旧数据,处理完了数据库就会删除缓存,保证了数据最终一致性,(所以比较推荐这种操作)

 

4、解决

一般使用延迟双删和删除重试机制

延迟双删

一个读线程发现缓存中没有对应的数据,去查库,在查询完毕准备更新缓存期间,另一个写线程完成了写库以及对缓存的删除此时数据库中的数据是新数据,而读线程将缓存更新为旧数据),一般情况下,这种事件的发生概率很低,但是使用延迟双删可以规避掉这种问题,进一步提高数据的一致性,但缺点就是性能会有所下降

那具体的超时时间要根据你具体的业务来定,一般设置几秒足够了

队列+重试机制

上述情况都会有删除Redis失败的情况,可以使用重试机制

比如重试三次,三次都失败则记录日志到数据库并发送警告让人工介入在高并发的场景下,重试最好使用异步方式,比如发送消息到 mq 中间件,实现异步解耦

也就是选择异步发送消息到MQ,线程监听MQ执行重试删除。

 

62bd2f223be64a768574793d5e4feb1c.png

Cannal解耦

但这里的MQ代码过于耦合,需要解耦的话,就可以将他们提取出来,使用阿里的一款开源框架canal主要用途是基于MySQL数据库增量日志解析,提供增量数据订阅和消费Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端,过程也就是执行失败后,异步发送消息到MQ,Canal客户端监听到MQ消息执行重试

通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。 MQ消息中间可以采用RocketMQ来实现推送

 

3b04924a27fc454fbc99cc0906f248b4.png

 

http://www.lryc.cn/news/492242.html

相关文章:

  • 基于微信小程序的教室预约系统+LW示例参考
  • Linux 安装 Git 服务器
  • 总结:Yarn资源管理
  • Python学习34天
  • 深入浅出 WebSocket:构建实时数据大屏的高级实践
  • 三开关VUE组件
  • SpringCloud+SpringCloudAlibaba学习笔记
  • 牛客小白月赛105(A~E)
  • OSPF协议整理
  • Java中的多线程
  • 什么是聚簇索引、非聚簇索引、回表查询
  • 探索 Spring 框架核心组件:构建强大 Java 应用的基石
  • Android 13 Aosp 默认允许应用动态权限
  • 【C++知识总结1】c++第一篇,简单了解一下命名空间是什么
  • 从0开始深度学习(32)——循环神经网络的从零开始实现
  • GitLab使用操作v1.0
  • cuda conda yolov11 环境搭建
  • 解决SpringBoot连接Websocket报:请求路径 404 No static resource websocket.
  • element-plus的组件数据配置化封装 - table
  • 【二维动态规划:交错字符串】
  • goframe开发一个企业网站 MongoDB 完整工具包18
  • 在vue中,根据后端接口返回的文件流实现word文件弹窗预览
  • 动态规划之背包问题
  • 【Python】 深入理解Python的单元测试:用unittest和pytest进行测试驱动开发
  • Java集合1.0
  • Leetcode 336 回文对
  • 实现一个可配置的TCP设备模拟器,支持交互和解析配置
  • 算法的空间复杂度
  • 自定义协议
  • 在 Taro 中实现系统主题适配:亮/暗模式