如何保证MySQL与Redis缓存的数据一致性?

文章目录

  • 一、引言
  • 二、场景来源
  • 三、高并发解决方案
    • 1. 先更新缓存,再更新数据库
    • 2. 先更新数据库,再更新缓存
    • 3. 先删除缓存,再更新数据库
    • 4. 先更新数据库,再删除缓存
    • 小结
  • 四、拓展方案
    • 1. 分布式锁与分布式事务
    • 2. 消息队列
    • 3. 监听binlog
  • 五、总结
  • 六、参考文章

一、引言

在现代互联网应用中,高并发场景下的数据访问是一个常见的挑战。为了提高数据访问速度,通常会使用 Redis 作为缓存层,但这也会带来数据一致性的难题。在四月份的时候,我参考现有资料编写了一篇数据库和缓存一致性处理方案的文档(原文链接:如何保证数据库、缓存的双写一致?),但总觉得内容有点空洞,介绍不够彻底。
本文将尝试重新介绍一下该问题。

二、场景来源

传统的系统通常基于 MySQL 和 Java 开发,虽然它们在数据持久化和事务处理方面表现出色,但在高并发场景下,单纯依赖数据库已经难以满足快速响应和高吞吐量的需求。而在现代互联网应用中,高性能和高可用性是系统设计的关键目标。
在这里插入图片描述
为了应对这一挑战,越来越多的系统开始引入 Redis 作为缓存层,以提升数据访问速度和系统整体性能。然而随着 Redis 的引入,读取数据的流程也随之变化。
在这里插入图片描述
如果是只读系统,这个流程也不错。但在高并发读写系统中,这个流程就有待完善啦!

三、高并发解决方案

引入Redis后,任何缓存数据的变更都可能会涉及如下三个操作:更新数据库、更新缓存和删除缓存。如果使用排列组合,可能的解决方案有四种:

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

我们逐个分析上述方案。

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

该方案的步骤如下:
在这里插入图片描述
如果更新缓存成功后,数据库更新失败,就会出现数据库为旧值,缓存为新值的情况。后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持脏数据(数据库中的值为旧值,而Redis缓存为新值),业务应该以数据库数据为准。
在这里插入图片描述
如果更新缓存成功,数据库更新失败,我们重新更新缓存呢?
抛开重新更新缓存时,要单表或多表重新查询数据,再更新数据带来的潜在性能问题,我们直接使用旧值更新,还可能更新失败,也有其他请求更新数据再次陷入脏数据的情况。
在这里插入图片描述
只要缓存进行了更新,后续的读请求在更新数据库前、更新数据库失败并重新更新缓存成功前,如果命中缓存,返回的数据都是未落库的脏数据。
结论:该方案不考虑

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

该方案的步骤如下:
在这里插入图片描述
如果数据库更新成功,缓存更新失败,会出现数据库为最新值,缓存为旧值的情况。后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持数据不一致!
就算上述更新数据库、更新缓存的操作都成功,还是存在并发引发的一致性问题:
在这里插入图片描述
如上图,可以看到经过两次更新后,数据库n更新为3,而缓存n更新为2。在并发读写的场景下,数据存在不一致性问题。
结论:该方案不考虑

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

该方案的步骤如下:
在这里插入图片描述
这是一种很常见的方法。它逻辑较为简单,也易于理解和实现,理论上删除旧缓存后,下次读取时将从数据库获取最新数据。
但在高并发的极端情况下,删除缓存成功后,如果再有大量的并发请求进来,那么请求便会直接到达数据库,对数据库造成巨大的压力。即便使用了一些并发访问策略保障了只有一个请求到达数据库,那也相当于上述第一步的删除的数据又重新加载到Redis中,而且此方案还可能会发生数据不一致性问题。
在这里插入图片描述
通过上图发现删除缓存后,如果有并发读请求进来,那么查询缓存肯定是不存在,则去读取数据库。此时更新数据库n=2的操作还未完成,所以读取到的仍然是旧值n=1。设置缓存n=1后,更新数据库n=2完成。此时数据库n是新值2,而缓存是旧值1,出现了数据不一致的问题。
对此,我们采用延时双删策略优化。即在更新数据库之后,先延迟等待一会儿(等待时间参考该读请求的响应时间+几十毫秒),再继续删除缓存。这样做的目的是确保读请求结束(它已经在数据库中读取到了旧数据,后续会在该请求中更新缓存),写请求可以删除读请求造成的缓存脏数据,保证再删除缓存之后的所有读请求都能读到最新值。
在这里插入图片描述
可以看出此优化的关键在于再次删除前需要等待多长时间。这个时间一般都是根据历史查询请求的响应时间判断的,但实际情况会有浮动。这也导致如果等待的时间过短,则仍然会出现数据不一致的情况;等待时间过长,则等待期间出现数据不一致的时间变长。
另外延时双删策略还需要考虑如果再次删除缓存失败的情况如何处理?
如果再次删除失败将导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!
这个在下文讨论。
结论:该方案不考虑

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

该方案的步骤如下:
在这里插入图片描述
对比以上方案,在大多数情况下,这种方案被认为是一个更好的选择。原因如下:

  • 数据的一致性:这种方法更倾向于保持数据的最终一致性,即使缓存删除失败,也能保证数据的一致性不会长期受损。
  • 用户体验:在方案3并发读写都成功的情况下,还是会出现数据不一致的情况,用户可能会一直看到旧数据,直到缓存过期。相比之下,该方案可以在某种程度上避免这种情况。

但该方案同样也会出现数据不一致性问题,如下图:
在这里插入图片描述

当数据库被更新后,缓存也被删除。接下来的出现读请求3.1和写请求3.2同时进来。
读请求先执行,读取缓存发现未命中后查询数据库并获取数值2,在准备更新缓存n=2时,写请求执行并完成了更新数据库和删除缓存,然后读请求才更新缓存n=2。此时,数据库为新值3,缓存为旧值2。
其实延迟双删策略,算是融合“先删除缓存,再更新数据库”和“先更新数据库,再删除缓存”的策略,可以解决大部分的数据一致性的业务逻辑处理问题。如果再次删除缓存失败,也可以通过重试机制进行一定程度的补救。
结论:推荐使用该方案

小结

从上面的四种方案看,似乎没有一种方案真正能解决并发场景下MySQL数据与Redis缓存数据一致性的问题。如果业务要求必须要满足强一致性,那么不管如何优化缓存策略,似乎都无法满足,那最好的办法是不用缓存。

强一致性:它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。
解决方案是读写串行化,而此方案会大大增加系统的处理效率,吞吐量也会大大降低。

另外在大型分布式系统中,其实分布式事务大多数情况都不会使用,因为维护成本太高了、复杂度也高。所以在分布式系统,我们一般都会推崇最终一致性,即这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。

四、拓展方案

这种双写的场景,其实还有另外三种方案,虽然应用场景并不多,但也确实提供了不同的思路,可以参考下。

1. 分布式锁与分布式事务

使用分布式事务可以确保两个操作的原子性。步骤如下:

  1. 开启事务。
  2. 更新数据库。
  3. 更新 Redis 缓存。
  4. 提交事务。

读写操作流程如下:

写操作读操作
写请求读请求

这种方式确保了数据库和缓存的一致性,适用于对数据一致性要求较高的场景。但它的实现比较复杂,增加了系统的复杂性。而且这种方式也会产生额外的性能开销。

2. 消息队列

使用消息队列可以异步处理数据更新操作,确保数据库和缓存的一致性。通过消息队列可以解耦数据更新的逻辑,提高系统的可扩展性和可靠性。
步骤如下:

  1. 更新数据库。
  2. 发送更新数据的消息到消息队列。
  3. 消费者从消息队列中读取更新数据的消息,删除 Redis 缓存。

3. 监听binlog

canal是阿里巴巴 MySQL binlog 增量订阅&消费组件。它基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。通过监听binlog,也可以实现双写一致性,步骤如下:

  1. 更新数据库
  2. 通过canal采集binlog日志,订阅更新信息并发送到消息队列
  3. 通过ACK手动机制确认处理这条更新消息,删除Redis缓存数据

在这里插入图片描述
尽管该方案看起来也不错了,但是因为引入额外的组件(如Canal、消息队列)复杂性增加了也不少,需要维护和监控这些组件的运行状态,保证组件运行正常。

五、总结

在高并发场景下,确保 MySQL 和 Redis 之间的数据一致性是分布式系统设计中的一个重要挑战。本文介绍了多种解决方案,每种方案都有其适用场景和优缺点。其实并没有一个最优解,更多的是需要综合考虑系统的具体需求、可用资源、性能要求、业务复杂性、维护成本等因素,最后确定出来的方案才是最适合的。
希望看到这里的你有所收获。

六、参考文章

  • 如何下保证MySQL数据库与Redis缓存数据一致性?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/59577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv调整图片对比度和亮度

在OpenCV中,为了改变图像的对比度和亮度,我们可以使用 cv2.convertScaleAbs() 方法。我们使用的方法的语法如下 cv2.convertScaleAbs(image,alpha,beta)其中image 是原始的输入图像。 # image cv2.imread(egg.jpg)alpha 是对比度值。为了降低对比度&am…

暴露IP地址会影响网络隐私安全吗?

​我的IP地址暴露后会影响隐私安全吗? 互联网飞速发展以来,短短数十年,我们的工作生活就不能够离开互联网。那么作为网络连接传递数据的门户——IP地址,大家都有一定的疑惑和好奇。其中关于自身安全的尤为重要,所以IP…

SQL面试题——蚂蚁SQL面试题 连续3天减少碳排放量不低于100的用户

连续3天减少碳排放量不低于100的用户 这是一道来自蚂蚁的面试题目,要求我们找出连续3天减少碳排放量低于100的用户,之前我们分析过两道关于连续的问题了 SQL面试题——最大连续登陆问题 SQL面试题——球员连续四次得分 这两个问题都是跟连续有关的,但是球员连续得分的难…

机器学习、深度学习面试知识点汇总

下面是本人在面试中整理的资料和文字,主要针对面试八股做浅显的总结,大部分来源于ChatGPT,中间有借鉴一些博主的优质文章,已经在各文中指出原文。有任何问题,欢迎随时不吝指正。 文章系列图像使用动漫 《星游记》插图…

深度学习--正则化

笔记内容侵权联系删 过拟合问题 过拟合问题描述:模型在训练集表现优异,但在测试集上表现较差。 根本原因:特征维度过多,模型假设过于复杂,参数过多,训练数据过少,噪声过多导致拟合出的函数几乎完美的对训练集做出预…

Jtti:服务器总是自动重启怎么办?

服务器总是自动重启可能是由于多种原因引起的,包括硬件故障、软件问题、配置错误或环境因素。以下是一些常见原因和相应的解决方案: 1. 硬件问题 电源故障:电源供应不稳定或电源模块故障可能导致服务器重启。 解决方案:检查电源供…

AXI接口的实现逻辑和底层原理,在FPGA中如何实现AXI接口,一篇文章足以搞明白!!!

AXI(Advanced eXtensible Interface)接口是一个点对点的接口,用于连接高性能的片上系统(SoC)中的处理器、外围设备、内存和其他IP核。以下是对AXI接口的详细解析,包括FPGA实现的原理、逻辑、速度以及详细的…

通过 SSH 隧道将本地端口转发到远程主机

由于服务器防火墙,只开放了22端口,想要通过5901访问服务器上的远程桌面,可以通过下面的方式进行隧道转发。 一、示例命令 这条代码的作用是通过 SSH 创建一个 本地端口转发,将你本地的端口(5901)通过加密的 SSH 隧道连接到远程服务器上的端口(5901)。这种方式通常用于在…

【Nginx】 bind() to 0.0.0.0:88 failed (13: Permission denied) 解决方法

问题描述 我在Nginx上添加一个端口号为88的虚拟主机, 重新启动Nginx报错: bind() to 0.0.0.0:88 failed (13: Permission denied) 解决方法 查阅资料,发现这类bind无权限问题,大多由SElinux引起。SELinux有三种模式&#xff0c…

C# Winform--SerialPort串口通讯(ASCII码发送)

1.代码部分 private SerialPort serialPort new SerialPort();private void button1_Click(object sender, EventArgs e){serialPort.BaudRate 9600;serialPort.Parity Parity.None;serialPort.StopBits StopBits.One;serialPort.DataBits 8;serialPort.PortName "C…

【学习】【HTML】块级元素,行内元素,行内块级元素

块级元素 块级元素是 HTML 中一类重要的元素&#xff0c;它们在页面布局中占据整行空间&#xff0c;通常用于创建页面的主要结构组件。 常见的块级元素有哪些&#xff1f; <div>: 通用的容器元素&#xff0c;常用于创建布局块。<p>&#xff1a;段落元素&#xf…

CTF攻防世界小白刷题自学笔记14

fileclude&#xff0c;难度&#xff1a;1&#xff0c;方向&#xff1a;Web 题目来源:CTF 题目描述:好多file呀&#xff01; 给一下题目链接&#xff1a;攻防世界Web方向新手模式第17题。 打开一看&#xff0c;这熟悉的味道&#xff0c;跟上一篇文章基本一摸一样的&#xff…

微信小程序开发,仿小红书瀑布流实现

文章目录 1. 涉及到的知识点2. 功能描述3. 通用属性3. 代码实现过程4. 报错问题&#xff0c;解决方法5. 运行效果图 1. 涉及到的知识点 grid-view的使用官方文档指南&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/grid-view.html 2. 功能描述 Sk…

python习题练习

python习题 编写一个简单的工资管理程序系统可以管理以下四类人:工人(worker)、销售员(salesman)、经理(manager)、销售经理(salemanger)所有的员工都具有员工号&#xff0c;工资等属性&#xff0c;有设置姓名&#xff0c;获取姓名&#xff0c;获取员工号&#xff0c;计算工资等…

11. 观光景点组合得分问题 |豆包MarsCode AI刷题

观光景点组合得分问题 - MarsCode 题目要求我们计算一组观光景点的最高组合得分。每个景点都有一个评分&#xff0c;保存在数组 values 中。一对景点 (i < j) 的观光组合得分为 values[i] values[j] i - j&#xff0c;即两者评分之和减去它们之间的距离。 我们需要找到一…

自动化生成测试用例:利用OpenAI提升电商网站测试覆盖率

导语 自动化生成测试用例是软件测试领域一个强大的应用&#xff0c;通过OpenAI的语言模型&#xff0c;测试工程师可以快速生成高质量的测试用例&#xff0c;尤其是在处理边界条件和极端情况时&#xff0c;提升测试覆盖率。本篇文章将结合一个典型的电商网站案例&#xff0c;介绍…

ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;“魅力”繁峙宣传网站系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了“魅力”繁峙宣传网站系统的发展&#x…

基于Matlab的碎纸片的自动拼接复原技术

碎纸片的自动拼接复原技术 摘要&#xff1a;破碎文件的拼接在司法物证复原、历史文献修复以及军事情报获取等领域都有着重要的应用。目前发现对碎纸片的拼接大部分由人工完成&#xff0c;准确率较高&#xff0c;但耗费大量人力财力及时间&#xff0c;效率很低。随着计算机技术的…

微信小程序的主体文件和页面文件介绍

一、主体文件 主体文件即全局文件&#xff0c;作用于整个小程序&#xff0c;对每一个页面都有影响&#xff0c;必须放到项目的根目录下。 这里提示一下&#xff0c;项目创建选择的是ts less模版。 1、主体文件介绍 主体文件由三部分组成&#xff1a; 1、app.ts&#xff1…

Kafka-Eagle的配置——kafka可视化界面

通过百度网盘分享的文件&#xff1a;kafka-eagle-bin-2.0.8.tar.gz 链接&#xff1a;https://pan.baidu.com/s/1H3YONkL97uXbLTPMZHrfdg?pwdsltu 提取码&#xff1a;sltu 一、界面展示 二、软件配置 1、关闭kafka集群 kf.sh stop 2、将该软件上传到/opt/modules下 cd /opt…