如何保障 MySQL 和 Redis 的数据一致性?

数据一致性问题是如何产生的?

数据一致性问题通常产生于数据在不同的时间点、地点或系统中存在多个副本的情况,
系统只存在一个副本的情况下也完全可能会产生。

设想一下,你在一家连锁咖啡店有一张会员卡这张会员卡可以绑定两个账号(假设是微信号),这张卡记录了你的积分。
你在北京的分店购买了一杯拿铁,积分增加了5分。就是这么巧,同一时间,你的好朋友在上海的分店也买了一杯拿铁,积分也增加了5分。
按理来说用这张卡买了两杯咖啡,系统上应该给你增加10分,但是,如果北京分店的系统更新到中央数据库的操作延迟了,
上海分店的系统可能还没有得到最新的积分信息。
这时,上海分店计算总积分的方式就是:原来的总积分 + 5分,北京的分店的计算方式也是:原来的总积分 + 5分,这张卡总的就加了5分,这就是一种数据一致性问题。

即使在单个系统内部,也可能存在类似的问题。

假设咖啡店的积分记录系统在进行维护,而你在这个时间点进行了消费。
如果维护操作和积分更新操作没有被正确的协调,可能会导致你的消费没有被记录到积分中,引发数据一致性问题。

在上面这个例子中,数据一致性问题是由于信息更新在不同地点之间同步不及时造成的,而在单个系统中,
可能是因为操作的顺序和同步机制没有设计得当导致一致性问题。
解决这类问题通常需要引入事务管理、分布式锁、队列、事件驱动的更新机制等策略来确保数据的准确性和实时性。

迪米特法则

在软件架构设计中,遵循的原则是尽量保持简洁和避免不必要的复杂性,
这个原则被称作 “最少知识原则” (Principle of Least Knowledge),也被称为 “迪米特法则” (Law of Demeter)。
当它和架构设计中的简约原则联系起来时,你可能指的是 “简单性原则”“奥卡姆剃刀”(Occam’s Razor) 原则。
奥卡姆剃刀原则通常被解释为在没有明显差别的情况下,应该选择假设较少、假设简单的理论。

在软件架构的上下文中,这意味着在其他条件相同的情况下,应该选择更简单、组件更少的设计方案
这样做的好处包括减少潜在的错误来源、降低系统的维护成本、提高系统的可理解性,并且有时还能提高性能。

每引入一个新组件,就增加了系统的复杂度和潜在的维护负担。因此,设计时应当权衡新组件带来的好处与其引入的复杂度之间的关系。

回到原本的问题

MySQL 和 Redis 的数据一致性问题是如何产生的?

MySQL 和 Redis 的数据一致性问题通常来源于它们在系统架构中扮演的不同角色和特性。
MySQL 是一个关系型数据库,通常用于存储持久化数据,而 Redis 是一个内存中的数据结构存储,常用作缓存和消息代理。
它们之间的数据一致性问题主要产生于下面几个方面:

  1. 数据更新时机:当数据在 MySQL 中被更新后,如果 Redis 中缓存的同一份数据没有被同时更新,就会出现不一致的情况。用户可能从缓存中获取到了旧的数据。

  2. 复制延迟:如果你使用 Redis 的复制功能来增强数据的可用性和冗余,可能会遇到主从复制延迟的问题。在高负载情况下,从节点上的数据可能会落后于主节点,导致数据一致性问题。

  3. 缓存穿透:如果请求的数据在 Redis 中没有找到,就会查询 MySQL 数据库。如果大量此类请求发生,而数据实际上并不存在于数据库中,这可能会导致数据库层面的性能问题,进而影响数据的读取和写入一致性。

  4. 缓存失效策略:当缓存因为达到了设定的时间限制或空间限制而被清除,如果数据更新策略没有得到很好的处理,那么当下一次读取时,就会从 MySQL 中读取到新的数据,而这段时间内,读取操作可能会得到不一致的结果。

  5. 事务处理:MySQL 支持事务处理,可以通过 ACID 属性(原子性、一致性、隔离性、持久性)来保证操作的准确性。而 Redis 对事务的支持是有限的,尽管它有一定的事务操作命令,但没有严格的 ACID 属性保证。

  6. 系统故障:系统崩溃或网络故障可能会导致正在同步的数据丢失或不完整,这也会引起数据一致性问题。

如何保障 MySQL 和 Redis 的数据一致性?

确保 MySQL 和 Redis 数据一致性可以通过以下几个更加详细的解决方案来实现:

1、先写数据库后写缓存:
首先更新 MySQL 数据库,确保数据持久化后,再更新 Redis 缓存。
这样即使缓存更新失败,应用也可以从数据库中读取最新数据,然后重试更新缓存。例如:

try {// 更新数据库updateDatabase(record);// 同步更新缓存updateCache(record);
} catch(Exception e) {// 如果缓存更新失败,可以记录日志并进行重试或者使用其他补偿机制log.error("Error updating cache", e);// 可以选择重新尝试更新缓存或者放入队列稍后处理retryUpdateCache(record);
}

![&nbsp][nbsp]

2、事务消息:
使用本地事务配合事务消息中间件,比如 RocketMQ 的事务消息功能。
先发送预备消息,然后在本地事务中执行数据库操作,根据操作结果最后提交或回滚消息。
如果消息提交成功,则消费者监听到消息后更新 Redis 缓存。这样就能保证数据库操作和缓存更新的最终一致性。

3、缓存双删策略:
在更新数据库之前和之后都删除缓存,第一次删除是为了防止在更新数据库的过程时间窗口内有新的请求访问到旧的缓存数据,
第二次删除是为了处理在第一次删除之后到数据库更新期间产生的脏数据。

// 第一次删除缓存
deleteCache(key);// 更新数据库
updateDatabase(record);// 休眠一段时间,比如100ms,让前面的数据库操作和缓存删除操作完成
Thread.sleep(100);// 第二次删除缓存
deleteCache(key);

![&nbsp][nbsp 1]

缓存双删策略为什么要删除两次?

缓存双删策略就是在更新数据库记录之前和之后都执行一次缓存删除操作。
这个策略的目的是为了尽可能减少数据库与缓存间数据不一致的时间窗口,解决缓存数据可能出现的脏读问题。具体的原因如下:

a、预防更新期间的脏读:
当更新数据库记录之前先删除缓存,目的是为了防止在数据库更新期间,如果有新的请求进来,这个请求会因为缓存不命中而去读取数据库中的最新数据,并将其写入到缓存中。

b、处理更新期间的写请求:
即使进行了第一次缓存删除操作,但在数据库更新完成之前,新的读请求可能已经带着旧的数据重新填充了缓存(这是因为删除缓存和更新数据库之间还是有一个很短的时间窗口)。因此,在数据库更新之后再次删除缓存,可以确保如果有旧数据被写入缓存,那么这些旧数据也会被清除。

c、确保数据一致性:

第二次删除操作是为了确保任何在两次删除操作中间由于旧数据写入缓存的情况得到处理。
这样即使发生了这种情况,缓存中的旧数据最终也会被清除,新的读请求会再次触发缓存的重建,这次则会从数据库加载最新的数据。

简单来说,缓存双删策略是一种实践中比较简单有效的方法,用于降低缓存数据不一致的风险。
然而,这种策略也不能完全保证缓存和数据库数据的强一致性,因为在高并发场景下仍然存在极小的窗口期可能会导致脏读。
因此,在一些对数据一致性要求极高的场景中,可能需要其他更复杂的数据同步机制。

![&nbsp][nbsp 2]

4、发布/订阅模式:
在更新数据库后,发布一个事件到消息队列。有一个订阅了这个队列的缓存更新服务,它会监听这些事件,并且负责更新或者删除对应的缓存。

5、定时校对进程:
定期运行一个后台进程,该进程对数据库和缓存数据进行校对。如果发现不一致,将缓存数据更新为数据库中的最新状态。这种方法适用于对一致性要求不是非常实时的场景。

这些方法可以根据实际业务的需求和场景进行选择和调整,通常需要综合使用这些策略来达到较优的一致性保障。
在实践中,还需要对各种边界条件和异常情况进行详细的考虑,以确保系统的健壮性。

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记和面经,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的阿里大佬写的刷题笔记,让我offer拿到手软

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!@小郑说编程

[nbsp]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Ffenbushi%2FMySQL_Redis%2Fconsistency%2Ffirst_MySQL.png&pos_id=img-fdg0tjWU-1704726201602)
[nbsp 1]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Ffenbushi%2FMySQL_Redis%2Fconsistency%2Fdouble_del1.png&pos_id=img-xpIJro8b-1704726201892)
[nbsp 2]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Ffenbushi%2FMySQL_Redis%2Fconsistency%2Fdouble_del2.png&pos_id=img-bicyRUqp-1704726202539)

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

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

相关文章

ComfyUI报错AttributeError: module ‘cv2.gapi.wip.draw‘ has no attribute ‘Text‘

ComfyUI在安装comfyui-reactor-node插件,然后启动之后突然报错: AttributeError: module cv2.gapi.wip.draw has no attribute Text 这是怎么回事呢? 于是四处搜寻答案。 总之就是opencv-python版本的问题导致的。 我将有可能解决办法的方法进行了总结。 下面列出所有解…

linux 设备模型之类

我们在本章中要考察最后的设备模型概念是类.一个类是一个设备的高级视图, 它抽象出 低级的实现细节. 驱动可以见到一个 SCSI 磁盘或者一个 ATA 磁盘, 在类的级别, 它们都 是磁盘. 类允许用户空间基于它们做什么来使用设备, 而不是它们如何被连接或者它们如 何工作. 几乎所有的类…

操作系统实验二

实验二 观察Linux行为,使用proc文件系统 一、实验目的 学习Linux内核、进程、存储和其他资源的一些重要特征。读/proc/stat文件,计算并显示系统CPU占用率和用户态CPU占用率。(编写一个程序使用/proc机制获得以及修改机器的各种资源参数。需要…

优化改进YOLOv5算法之AKConv(可改变核卷积),即插即用的卷积,效果秒杀DSConv

1 AKConv原理 AKConv: Convolutional Kernel with Arbitrary Sampled Shapes andArbitrary Number of Parameters 摘要:基于卷积运算的神经网络在深度学习领域取得了令人瞩目的成果,但标准卷积运算存在两个固有的缺陷。一方面,卷积运算仅限于局部窗口,无法捕获其他位置的…

安卓上使用免费的地图OpenStreetMap

前一段使用了微信的地图,非常的好用。但是存在的问题是海外无法使用,出国就不能用了; 其实国内三家:百度,高德,微信都是一样的问题,当涉及到商业使用的时候需要付费; 国外除了谷歌…

51单片机介绍

1 单片机简介 单片机,英文Micro Controller Unit,简称MCU 内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能 单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设…

【Leetcode】227.基本计算器II

一、题目 1、题目描述 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。 你可以假设给定的表达式总是有效的。所有中间结果将在 [ − 2 31 , 2 31 − 1 ] [-2^{31}, 2^{31} - 1] [−2

2.6 KERNEL LAUNCH

图2.15在vecAdd函数中显示最终主机代码。此源代码完成了图2.6.中的骨架。2.12和2.15共同说明了一个简单的CUDA程序,该程序由主机代码和设备内核组成。该代码是硬接的,每个线程块使用256个线程。然而,使用的线程块的数量取决于向量&#xff08…

jenkins通过流水线自动部署项目(k8s部署)

参考:https://www.cnblogs.com/rb2010/p/16195443.html docker 拉取镜像到本地: docker pull docker.io/jenkins/jenkins:2.164配置卷挂载:使用nfs 参考:https://www.kuboard.cn/learning/k8s-intermediate/persistent/nfs.htm…

RT-DETR改进Shape-IoU损失函数:考虑边界框形状和比例的更准确的指标

💡本篇内容:RT-DETR改进Shape-IoU损失函数:考虑边界框形状和比例的更准确的指标 💡🚀🚀🚀本博客 改进源代码改进 适用于 RT-DETR 按步骤操作运行改进后的代码即可 💡参考论文地址:https://arxiv.org/abs/2312.17663 2023年12月最新发表论文:IoU最新的损失函数…

指针传参误区

C语言中指针作为形参传递时,func(*a, *b) 这种形式的话,是无法通过简单的 ab来修改的,在函数体内a的地址确实被修改成b的地址了,但是当函数执行结束时,a的地址会重新回到原本的地址里面&#xf…

卷积神经网络|猫狗分类系列--导入kaggle猫狗数据集

解决任何真实问题的重要一步是获取数据,Kaggle提供了大量不同数据科学问题的竞赛。 我们将从 https://www.kaggle.com/competitions/dogs-vs-cats/data 下载猫狗数据集,并对其进行一定的操作,以正确的导入到我们的计算机,为接下…

四 视图

1、实验目的 理解SQL成熟设计基本规范,能够熟练使用SQL语句来创建需要的视图,定义数据库外模式,并能使用所创建的视图实现数据管理。 2、实验内容及要求 使用SQL对数据库进行各类查询数据操纵操作,掌握单行数据插入、多行数据插…

设计模式的艺术P1基础—第1章 概述

刘伟,2020 概述:4部分,26章。 P1:基础(1-2章) P2:创建型设计模式(创建艺术,3-8章) P3:结构型设计模式(组合艺术,9-15章) P4:行为型设计模式&…

【设计模式】 模板方法模式

前言 1. 单例模式(Singleton Pattern):保证一个类只有一个实例,并提供一个全局的访问点。 2. 工厂模式(Factory Pattern):定义一个创建对象的接口,但由子类决定要实例化的类是哪一…

2_工厂设计_工厂方法和抽象工厂

工厂设计模式-工厂方法 1.概念 工厂方法模式(Fatory Method Pattern ) 是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。 在工厂方法模式中用户只需要关心所需产品对应的工厂,…

css——box-shadow阴影效果

/* box-shadow:内外阴影(inset:内阴影,水平阴影,垂直阴影,阴影模糊距离,阴影大小,阴影颜色) */box-shadow: 0 0 50px #ccc; 引用来源:box-shadow详解_box-shadow属性详解-CSDN博客

AUTOSAR开发文档

目录 目录 状态机电源管理开发... I 文档... I 1. 综述... 1 2. 系统硬件架构图... 1 3. 状态机设计方案... 2 4. 电源管理方案... 4 综述 本文档主要描述了MCU芯片TC297的AUTOSAR方案。MCU的基础软件由AUTOSAR软件实现&#xff0…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

微信小程序实战-01翻页时钟-1

文章目录 前言需求分析功能设计界面设计界面结构设计界面样式设计 逻辑设计 单页功能实现运行结果 前言 我经常在手机上用的一款app有一个功能是翻页时钟,基于之前学习的小程序相关的基础内容,我打算在微信小程序中也设计一个翻页时钟功能,J…