MySQL与Redis的缓存一致性问题

devtools/2025/3/15 6:32:40/

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.ppmy.cn/devtools/167223.html

相关文章

3.14学习总结

今天完成了几道关于二叉树的算法题 关于二叉树的最小最大深度和数据流中的第k大元素,用到优先队列,学习了有关java的基础知识,学习了双指针法。

Windows11使用CMD命令行从零开始创建一个Flask项目并使用虚拟环境

在 Windows 11 中,你可以使用 CMD 命令 创建一个 Flask 项目,并使用 虚拟环境(venv 或 pipenv) 进行管理。以下是从零开始的完整步骤: 方法 1:使用 venv 创建虚拟环境 1. 打开 CMD 按 Win R,…

Python 逆向工程:2025 年能破解什么?

有没有想过在复杂的软件上扭转局面?到 2025 年,Python 逆向工程不仅仅是黑客的游戏,它是开发人员、安全专业人员和好奇心强的人解开编译代码背后秘密的强大方法。无论您是在剖析恶意软件、分析 Python 应用程序的工作原理,还是学习…

基于javaweb的SpringBoot杂物商城系统设计与实现(源码+文档+部署讲解)

技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

使用 Excel 实现绩效看板的自动化

引言 在日常工作中,团队的绩效监控和管理是确保项目顺利进行的重要环节。然而,面临着以下问题: ​数据分散:系统中的数据难以汇总,缺乏一个宏观的团队执行情况视图。​看板缺失:系统本身可能无法提供合适…

【算法】 【c++】字符串s1 中删除所有 s2 中出现的字符

【算法】 【c】字符串s1 中删除所有 s2 中出现的字符 eg: s1:“helloworld” s2:“wd” 删除后:s1:“helloorl” 1 双循环匹配并删除–>时间复杂度O(n^2) string 里面的删除函数–>erase std::string::erase 是 C 标准库中用于删除字符串中字符…

​​vue-router编程式导航,params传参拿不到

vue-router在4.14版本就废弃这种传参方式了 原因:​​这种传参本来就不是官方推荐的,比如页面刷新会引起参数丢失​ 官方解释 官方解释翻译后的截图: 解决(官方推荐了很多种解决方式,下面列举出快捷高效的修改方式&a…

PHP火山引擎API签名方法

一、前置准备 在开始签名之前,需要准备以下信息: Access Key ID(AK):请求火山引擎OpenAPI的安全凭证之一。Secret Access Key(SK):与AK成对使用,用于签名计算。请求参数…