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

MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性,很多应用程序将MySQL和Redis一起使用,其中MySQL作为主要的持久存储,而Redis作为主要的缓存。在这种情况下,应用程序需要确保MySQL和Redis中的数据是同步的,以确保数据的一致性。

什么是一致性

“数据一致”一般指的是:缓存中有数据,缓存的数据值=数据库中的值。但根据缓存中是有数据为依据,则“一致”可以包含两种情况:

1)缓存中有数据,缓存的数据值=数据库中的值。

2)缓存中本没有数据,数据库中的值=最新值(有请求查询数据库时,会将数据写入缓存,则变为上面的“一致”状态)。

“数据不一致”:缓存的数据值≠数据库中的值;缓存或者数据库中存在旧值,导致其他线程读到旧数据。

在这里插入图片描述
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

导致数据不一致的原因?

1) 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库;

2)读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新,数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题;

3)这个业务场景,主要是解决读数据从Redis缓存,一般都是按照下图的流程来进行业务操作。

在这里插入图片描述

应对策略

针对缓存更新问题,提出了一个旁路缓存的缓存更新套路,这个策略分为以下三种场景:

1)失效:应用程序先从缓存取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

2)命中:应用程序从缓存中取数据,取到后返回。

3)更新:先把数据存到数据库中,成功后,再让缓存失效。

不管是先删缓存再更新数据库还是先更新数据库再删缓存,都会导致缓存跟数据不一致问题!

先写MySQL,再写Redis

在这里插入图片描述
先写Redis,再写MySQL
在这里插入图片描述
先删除Redis,再写MySQL
在这里插入图片描述
先写 MySQL,再删除 Redis
在这里插入图片描述
不管是先写数据库,再删除缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。

现有的大部分业务场景下大多采用读写分离的操作来提升数据库吞吐量,但是并发读写访问的时候,对缓存和数据库相互交叉执行操作,则会出现数据不一致问题。

在进行数据更新时,就涉及到先更新缓存还是先更新数据库了,其实两种方式都有数据一致性问题:

举个例子:假如业务A为写请求,业务B为读请求

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

步骤1:业务A先更新数据库,此时该业务线由于宕机或者其他原因延迟没有继续进行。

步骤2:业务B读取数据,读取的是缓存中的旧数据。

步骤3:业务A恢复过来,更新缓存

可以看到,由于写请求延迟,可能会读到旧的缓存数据。

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

步骤1:业务A先删除缓存

步骤2:业务B进入,业务B发现缓存中没有数据,直接从数据库中进行读取,读到了数据库中的旧数据

步骤3:业务A更新数据库并返回。

可以看到,由于写请求延迟,可能读到旧的数据库数据。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

解决方案

(1)读写请求串行化

最为简单的一种方法,写请求在更新之前需要先获得分布式锁,获取到锁才能去更新数据库,获取不到则进行等待,超时直接返回更新失败。更新完数据库后更新缓存,如果更新失败,放到内存队列中进行重新尝试。读请求则同样需要获得锁,然判断缓存中是否有数据,有则直接读缓存,没有则直接读数据库,并更新缓存。

这种方案可以保证数据的一致性。但是会降低系统吞吐量(等待时间长),这在需要数据强一致的情况下适用。(银行转账)

(2)删除缓存

  • 1.先删除缓存,后更新数据库
  • 2.先更新数据库,后删除缓存

先删除缓存,后更新数据库,第二步操作失败,数据库没有更新成功,那下次读缓存发现不存在则从数据库中读取,并重建缓存,此时数据库和缓存依日保持一致。

但如果是先更新数据库,后删除缓存,第二步操作失败,数据库是最新值,缓存中是旧值,发生不致。所以,这个方案依旧存在问题。

总之,和前面提到的问题类似,第二步失败依旧有不一致的风险

我们再来看[并发]问题,这个问题是我们需要关注的[重点]

先更新数据库,后删除缓存

依旧是 2 个线程并发[读写]数据

1.缓存中 X 不存在 (数据库 X = 1)
2.线程 A 读取数据库,得到目值 (X = 1)
3.线程 B 更新数据库 (X = 2)
4.线程 B 删除缓存
5.线程 A 将日值写入缓存 (X = 1)

最终 X的值在缓存中是 1 (日值) ,在数据库中是 2(新值),也发生不一致

这种情况[理论]来说是可能发生的,但实际真的有可能发生吗?

其实概率[很低],这是因为它必须满足 3 个条件

1.缓存刚好已失效
2.读请求 + 写请求并发
3.更新数据库 + 除缓存的时间 (步 3-4) ,要比读数据库 + 写缓存时间短(步 2 和5)

仔细想一下,条件 3 发生的概率其实是非常低的因为写数据库一般会先[加锁],所以写数据库,通常是要比读数据库的时间更长的这么来看,[先更新数据库 + 再删除缓存]的方案,是可以保证数据一致性的。

所以,我们应该采用这种方案,来操作数据库和缓存

如何保证两步都执行成功?

无论是更新缓存还是删除缓存,只要第二步发生失败,那么就会导致数据库和缓存不一致。
保证第二步成功执行,就是解决问题的关键.

程序在执行过程中发生异常,最简单的解决办法是什么?

答案是:异步重试

  • 如果是同步重试,立即重试很大概率还会失败,[重试次数]设置多少才合理?

  • 重试会一直[占用]这个线程资源,无法服务其它客户端请求

  • 异步其实就是把重试请求写到消息队列中,然后由专门的消费者来重试,直到成功。

为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存

到这里你可能会问,写消息队列也有可能会失败啊? 而且,引入消息队列,这又增加了更多的维扩成本,这样做值得吗?

这个问题很好,但我们思考这样一个问题:如果在执行失败的线程中一直重试,还没等执行成功,此时如果项目[重启]了,那这次重试请求也就[丢失]了,那这条数据就一直不一致了

所以,这里我们必须把重试消息或第二步操作放到另一个[服务]中,这个服务用[消息队列]最为合适。

  • 消息队列保证可靠性: 写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)
  • 消息队列保证消息成功投递: 下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者 (符合我们重试的需求)

至于写队列失败和消息队列的维护成本问题

  • 写队列失败: 操作缓存和写消息队列,[同时失败]的概率其实是很小的维护成本:

  • 我们项目中一般都会用到消息队列,维护成本并没有新增很多

参考资料:https://www.zhihu.com/question/319817091
https://www.jb51.net/database/285448xty.htm
https://baijiahao.baidu.com/s?id=1706150811910444110&wfr=spider&for=pc

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java项目作业~ 通过html+Servlet+MyBatis,完成站点信息的添加功能

需求: 通过htmlServletMyBatis,完成站点信息的添加功能。 以下是站点表的建表语句: CREATE TABLE websites (id int(11) NOT NULL AUTO_INCREMENT,name char(20) NOT NULL DEFAULT COMMENT 站点名称,url varchar(255) NOT NULL DEFAULT ,…

系统架构设计专业技能 · 软件工程之软件测试与维护(六)【系统架构设计师】

系列文章目录 系统架构设计专业技能 网络规划与设计(三)【系统架构设计师】 系统架构设计专业技能 系统安全分析与设计(四)【系统架构设计师】 系统架构设计高级技能 软件架构设计(一)【系统架构设计师…

数据统计与可视化的Dash应用程序

在数据分析和可视化领域,Dash是一个强大的工具,它结合了Python中的数据处理库(如pandas)和交互式可视化库(如Plotly)以及Web应用程序开发框架。本文将介绍如何使用Dash创建一个简单的数据统计和可视化应用程…

FPGA应用学习笔记-----复位电路(二)和小结

不可复位触发器若和可复位触发器混合写的话,不可复位触发器是由可复位触发器馈电的。 不应该出现的复位,因为延时导致了冒险,异步复位存在静态冒险 附加素隐含项,利用数电方法,消除静态冒险 这样多时钟区域还是算异步的…

MYSQL作业二

首先,查询数据库并是选择一个数据库打开 show table; use sys 第二部,创建一个表格 CREATE TABLE employee ( 部门号 int(11) not null, 职工号 int(11) not null, 工作时间 date not null, 工资 float(8,2) not null, 政治面貌 varchar(20) not…

一个完美的自动化测试框架应该怎么写?

一、什么是自动化测试框架? 自动化测试框架是为自动化测试用例或者脚本提供执行环境而搭建的基础设施。自动化测试框架有助于有效地开发、执行和报告自动化测试用例。 优点: 代码复用 提高测试效率 更高的测试覆盖率 维护成本低 更早发现和记…

Spring对象装配

在spring中,Bean的执行流程为启动spring容器,实例化bean,将bean注册到spring容器中,将bean装配到需要的类中。 既然我们需要将bea装配到需要的类中,那么如何实现呢?这篇文章,将来阐述一下如何实…

windows以管理员的身份运行CMD

电脑在装系统的时候,我的用户不是最高权限的管理员。 今天在工作的时候,使用CMD。运行失败,提示我需要使用管理员的身份运行CMD才可以。 使用右键点击左下角的windows图标 选择红框标注的那项。 以普通身份运行: 以管理员身份运行…

HTML5的介绍和基本框架

目录 HTML5 HTML5介绍 HTML5的DOCTYPE声明 HTML5基本骨架 html标签 head标签 body标签 title标签 meta标签 在vscode中写出第一个小框架 HTML5 HTML5介绍 HTML5是用来描述网页的一种语言,被称为超文本标记语言。用HTML5编写的文件,后缀以.ht…

24届近3年上海电力大学自动化考研院校分析

今天给大家带来的是上海电力大学控制考研分析 满满干货~还不快快点赞收藏 一、上海电力大学 学校简介 上海电力大学(Shanghai University of Electric Power),位于上海市,是中央与上海市共建、以上海市管理为主的全日…

【vue】简洁优雅的火花线、趋势线

来由 在github发现个好看易用的vue趋势线组件,特此记录。 效果 趋势图生成后效果如上,线条为渐变色,可设置是否平滑。具体线条走势,根据数据动态生成。 使用 安装 npm i vuetrend -S 引入 import Vue from "vue"…

无涯教程-Perl - sprintf函数

描述 此函数使用FORMAT基于LIST中的值返回格式化的字符串。本质上与printf相同,但是返回格式化的字符串而不是将其打印。 语法 以下是此函数的简单语法- sprintf FORMAT, LIST返回值 此函数返回SCALAR(格式化的文本字符串)。 例 以下是显示其基本用法的示例代码- #!/us…

重要日期提醒软件是哪个?帮你记住重要日程的提醒软件

生活、工作、学习中有众多重要的日期,如生日、纪念日、会议、截止日期等,它们承载着我们珍贵的回忆和重要的任务,因此绝对不能忘记。然而在信息爆炸的时代,我们的生活和工作节奏都是非常快的,时常会忘记这些重要的日程…

CentOS 7 下 Keepalived + Nginx 实现双机高可用

CentOS 7 下 Keepalived Nginx 实现双机高可用 文章目录 CentOS 7 下 Keepalived Nginx 实现双机高可用服务器准备服务信息服务架构 服务安装nginxKeepalived 服务配置nginxKeepalived 启动服务nginxkeepalived 服务验证查看 VIP 状态CURL 命令访问浏览器访问 高可用验证停止…

ZDH-wemock模块

本次介绍基于版本v5.1.1 目录 项目源码 预览地址 安装包下载地址 wemock模块 wemock模块前端 配置首页 配置mock wemock服务 下载地址 打包 运行 效果展示 项目源码 zdh_web: https://github.com/zhaoyachao/zdh_web zdh_mock: https://github.com/zhaoyachao/z…

了解51单片机

目录 51单片机名字的由来 主要功能 1.控制处理 2.数据处理 3.通信 4.定时计数 51单片机的组成 1.中央处理器CPU 2.存储器RAM、只读存储器ROM 3.I/O口和中断系统 4.显示驱动电路、A/D转换器 5.定时器/计数器、脉宽调制电路、模拟多路转换器等电路 单片机的应用领域(…

论文阅读:《Waymo Public Road Safety Performance Data》

文章目录 1 背景2 方法2.1 数据来源2.2 碰撞数据 3 碰撞事件分析4 讨论 1 背景 这篇文章是讲waymo道路安全性能数据分析的,主要想表达的是waymo自动驾驶系统在安全上面的出色表现,以向政府、大众提高自己产品的公信力。 这篇文章分析的数据是自从2019年到…

Field injection is not recommended

文章目录 1. 引言2. 不推荐使用Autowired的原因3. Spring提供了三种主要的依赖注入方式3.1. 构造函数注入(Constructor Injection)3.2. Setter方法注入(Setter Injection)3.3. 字段注入(Field Injection) 4…

日志采集分析ELK

这里的 ELK其实对应三种不同组件 1.ElasticSearch:基于Java,一个开源的分布式搜索引擎。 2.LogStash:基于Java,开源的用于收集,分析和存储日志的工具。(它和Beats有重叠的功能,Beats出现之后&a…

【单片机】DS2431,STM32,EEPROM读取与写入

芯片介绍: https://qq742971636.blog.csdn.net/article/details/132164189 接线 串口结果: 部分代码: #include "sys.h" #include "DS2431.h"unsigned char serialNb[8]; unsigned char write_data[128]; unsigned cha…