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

MySQL与Redis的缓存一致性问题

MySQL与Redis的缓存一致性问题

前言

在学习中,为了提高数据的读取效率,我们往往会使用Redis来作为MySQL数据的缓存,那么,自然就产生了二者间数据的一致性问题。

想要对MySQL和Redis进行数据处理,自然会产生以下问题:

  • MySQL与Redis操作的时序问题
    • 更新与删除的选择及时序问题

下面我们来一一分析:

先操作MySQL

先删除MySQL中数据

这种情况下,当我们选择先将MySQL中的数据删除时,如果后续写入新数据失败,新数据就很有可能会丢失,我们就完全失去了这条数据,这是难以接受的。所以我们还是来看看先更新MySQL的情况吧:

先更新MySQL中数据

这种情况下,当客户端修改一个数据时,我们先将MySQL中数据更新为最新状态,然后再操作Redis(如果第一步更新MySQL数据失败,就不会继续操作Redis了,此时MySQL和Redis中的数据都还是老数据,也是处于一致状态,可以接受):

再更新Redis中数据

试想这样一种情况:

线程A和线程B按以下顺序执行:

  1. 线程A缓存未命中,然后从MySQL中读到数据c=1
  2. 线程B想将数据c=1修改为c=2
  3. 线程B先更新MySQL成功,再更新Redis数据c=2(不论更新Redis成功与否)
  4. 线程A写回Redis数据c=1

可以看到,此时MySQL中是新数据c=2,Redis中却是老数据c=1,处于不一致状态。在Redis中该数据自动过期,或者再次更新该数据之前,客户端都会读到Redis中的老数据(脏数据)。

不过读操作,往往比写操作更快速,也就是说大多数情况下,线程A等线程B操作完了再写回Redis的情况不会出现。

但是,对于并发更新的情况,可能会出现多个线程并发更新Redis数据,导致老数据覆盖新数据的情况,也会造成不一致状态。

再删除Redis中数据

与上面“再更新Redis中数据”的情况类似,可能会出现线程B删除完Redis数据后,线程A又写回老数据的情况。

但是,对于并发更新的情况,就算多个线程并发删除Redis数据,也能够保证删除老数据,不会造成不一致状态。

当然,如果删除Redis失败了,Redis中还是会留下老数据,造成不一致状态。

先操作Redis

先删除Redis中数据

再更新MySQL中数据

试想这样一种情况:

线程A和线程B按以下顺序执行:

  1. 线程B想将数据c=1修改为c=2
  2. 线程B先删除Redis中数据成功
  3. 线程A缓存未命中,然后从MySQL中读到数据c=1
  4. 线程B更新MySQL中数据c=2
  5. 线程A写回Redis数据c=1

可以看到,此时MySQL中是新数据c=2,Redis中却是老数据c=1,处于不一致状态。在Redis中该数据自动过期,或者再次更新该数据之前,客户端都会读到Redis中的老数据(脏数据)。

此外,对于并发更新的情况,可能会出现多个线程并发更新MySQL数据,导致老数据覆盖新数据的情况,也会造成不一致状态。

再删除MySQL中数据

这种情况下,当我们选择将MySQL中的数据删除时,如果后续Redis中的新数据丢失(磁盘存储相对内存存储的可靠性更高),我们就完全失去了这条数据,这是难以接受的。

总结

Cache-Aside (旁路缓存)

综上所述,我们往往会选择 先更新MySQL中数据-再删除Redis中数据 的方案。

以上所述所有的方案,有一个统称:Cache-Aside (旁路缓存)

直读 与 同步/异步直写

此外,如果我们将缓存的业务直接从其它业务代码中抽取出来,给其它业务提供一个缓存抽象层,将缓存的操作全部放在这个缓存抽象层中独立存在,也就是缓存的解耦。

这样,主体业务就只需要与抽出来的缓存层进行交互,无需再关心数据的一致性,直接读/写缓存层,也就是直读直写

直读(Write-Through)

客户端(也可以是其他业务层)直接读取Redis缓存,如果缓存未命中,从MySQL中读取数据后,再写回Redis,然后再返回给客户端。

直写(Write-Through)

客户端(也可以是其他业务层)直接先更新Redis缓存,然后再更新MySQL,然后再返回数据给客户端。

上面的是同步直写,如果在更新MySQL的同时,异步将数据返回给客户端,那么就叫异步直写(Write-Behind)

canal

此外,为了提高系统的可用性,我们可以配合一些成熟的中间件,例如:

使用Canal监控MySQL的binlog日志,自动通知缓存业务MySQL中的数据进行了什么修改,可以让我们的系统迅速知道MySQL的数据改动,延迟极低。

在缓存业务中,我们往往还可以引入消息队列,提高数据传输的可靠性,例如:

当Canal监控到MysQL数据改动后,将监控数据发送到RabbitMQ,由订阅了相关Topic的缓存业务消费监控数据并进行处理,可以有效避免数据传输中失败、丢失等问题。


http://www.kler.cn/a/584563.html

相关文章:

  • 【linux】文件与目录命令 - stat
  • 使用Python在Word中生成多种不同类型的图表
  • JVM类加载机制和双亲委派
  • 江科大51单片机笔记【16】AD/DA转换(下)
  • Arbitrum之智能合约
  • Java 虚拟机优化指南:CMS垃圾回收器参数调优与性能监控工具详解
  • 【数据结构】初识集合框架及背后的数据结构(简单了解)
  • uniapp移动端图片比较器组件,仿英伟达官网rtx光追图片比较器功能
  • Java --- 根据身份证号计算年龄
  • 《基于大数据的营养果蔬推荐系统的设计与实现》开题报告
  • makefile详解
  • Discuz建站教程之论坛头部logo跳转链接怎么修改?
  • HCIA复习实验拓扑详细版
  • mysql下载与安装、关系数据库和表的创建
  • DeepSeek-R1:开源大模型的技术革命与行业影响分析
  • Python第二十课:生成对抗网络 | AI创造力觉醒
  • 基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)
  • 【每日学点HarmonyOS Next知识】上下拉动作、图片预览、组件边距、this获取、svg旋转
  • 工业物联网平台系列技术介绍-分类、聚类、逻辑回归
  • 中服云官网改版上线,全力聚焦工业物联网平台IIOT领域