Redis数据一致性问题的三种解决方案

news2024/12/23 9:31:28

Redis数据一致性问题的三种解决方案

1、首先redis是什么

Redis(Remote Dictionary Server ),是一个高性能的基于Key-Value结构存储的NoSQL开源数据库。大部分公司采用Redis来实现分布式缓存,用来提高数据查询效率。

2、为什么会选Redis

在Web应用发展的初期,系统的访问和并发并不高,交互也比较少。但随着业务的扩大,访问量的提升,使得服务器负载和关系型数据库出现瓶颈,而导致瓶颈的源头,主要体现在磁盘IO上。随着互联网的进一步发展,对系统性能有了更高的要求,Redis的出现,解决了很多问题。至于我们为什么要选择Redis,我总结为以下六个原因:

1)、基于内存存储,可以降低对关系型数据库的访问频次,从而缓解数据库压力

2)、数据IO操作能支持更高级别的QPS,官方发布的指标是10W;

3)、提供了比较多的数据存储结构,比如string、list、hash、set、zset等等。

4)、采用单线程实现IO操作,避免了并发情况下的线程安全问题。

5)、可以支持数据持久化,避免因服务器故障导致数据丢失的问题

6)、Redis还提供了更多高级功能,比如分布式锁、分布式队列、排行榜、查找附近的人等功能,为更复杂的需求提供了成熟的解决方案。

3、 应用场景

缓存,作为Key-Value形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存

分布式锁,分布式环境下对资源加锁

分布式共享数据,在多个应用之间共享

排行榜,自带排序的数据结构(zset)

消息队列,pub/sub功能也可以用作发布者 / 订阅者模型的消息

4、redis用作缓存时

4.1、作为缓存使用流程
缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。
在这里插入图片描述
4.2、数据性一致性问题
例如我们使用Redis来作为缓存时,让请求先访问到Redis,而不是直接访问数据库。而在这种业务场景下,可能会出现缓存和数据库数据不一致性的问题。

在更新的时候,操作缓存和数据库无疑就是以下四种可能之一:

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

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

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

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

4.2.1、先更新缓存,再更新数据库
在这里插入图片描述

如果我成功更新了缓存,但是在执行更新数据库的那一步,服务器突然宕机了,那么此时,我的缓存中是最新的数据,而数据库中是旧的数据。

脏数据就因此诞生了,并且如果我缓存的信息(是单独某张表的),而且这张表也在其他表的关联查询中,那么其他表关联查询出来的数据也是脏数据,结果就是直接会产生一系列的问题。

4.2.2、先更新数据库,在更新缓存
在这里插入图片描述
只有等到缓存过期之后,才能访问到正确的信息。那么在缓存没过期的时间段内,所看到的都是脏数据。

以上两图中只要执行第二步时失败了,就必然会产生脏数据。

4.2.3、先删除缓存,在更新数据库
这种方式在没有高并发的情况下,是可能保持数据一致性的。

在这里插入图片描述
如果只有第一步执行成功,而第二步失败,那么只有缓存中的数据被删除了,但是数据库没有更新,那么在下一次进行查询的时候,查不到缓存,只能重新查询数据库,构建缓存,这样其实也是相对做到了数据一致性。

但如果是处于读写并发的情况下,还是会出现数据不一致的情况:
在这里插入图片描述
执行完成后,明显可以看出,1号用户所构建的缓存,并不是最新的数据,还是存在问题的

4.2.4、先更新数据库,在删除缓存
如果更新数据库成功了,而删除缓存失败了,那么数据库中就会是新数据,而缓存中是旧数据,数据就出现了不一致情况。
在这里插入图片描述
和之前一样,如果两段代码都执行成功,在并发情况下会是什么样呢?
在这里插入图片描述
还是会造成数据的不一致性。

但是此处达成这个数据不一致性的条件明显会比起其他的方式更为困难 :

  • 时刻1:读请求的时候,缓存正好过期

  • 时刻2:读请求在写请求更新数据库之前查询数据库,

  • 时刻3:写请求,在更新数据库之后,要在读请求成功写入缓存前,先执行删除缓存操作。

这通常是很难做到的,因为在真正的并发开发中,更新数据库是需要加锁的,不然没一点安全性~

一定程度上来讲,这种方式还是解决了一定程度上的数据不一致性问题的。

4.3、总结

以上四种方式无论选择那种方式,如果实在多服务或时并发的情况下,其实都是有可能产生数据不一致性的。

为了解决这个存在的问题有以下方式:

4.3.1、延迟双删
先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除。进行两次删除,且中间需要延迟一段时间
在这里插入图片描述
scss

public void write(String key,Object data){ // 延迟双删伪代码 
deleteRedisCache(key); // 删除redis缓存 
updateMysqlSql(obj); // 更新mysql 
Thread.sleep(100); // 延迟一段时间 
deleteRedisCache(key); // 再次删除该key的缓存 }

延迟双删的流程图:
在这里插入图片描述
解决这样的问题,其实最好的方式就是在执行完更新数据库的操作后,先休眠一会儿,再进行一次缓存的删除,以确保数据一致性

首先延迟删除的时间需要大于 1号用户执行流程的总时间

就是1号用户从数据库读取数据 写入缓存时间

4.3.2、通过发送MQ,在消费者线程去同步Redis
在这里插入图片描述
无论是更新缓存还是删除缓存,在同时操作缓存和数据库时,都无法保证两者都能一次性操作成功,所以我们最好的办法就是重试,这个重试并不是立即重试,因为缓存和数据库可能因为网络或者其它原因停止服务了,立即重试成功率极低,而且重试会占用线程资源,显然不合理,所以我们需要采用异步重试机制。

异步重试我们可以使用消息队列来完成,因为消息队列可以保证消息的可靠性,消息不会丢失,也可以保证正确消费,当且仅当消息消费成功后才会将消息从消息队列中删除。

  • 优点1:可以大幅减少接口的延迟返回的问题
  • 优点2:MQ本身有重试机制,无需人工去写重试代码
  • 优点3:解耦,把查询Mysql和同步Redis完全分离,互不干扰

4.3.3、Canal 订阅日志实现
当我们业务修改数据时,我们只需要更新数据库,无需修改缓存,那什么时候修改缓存呢?

以mysql为例,在数据库一条记录发生变更时就会生成一条binlog日志,我们可以订阅这种消息,拿到具体的数据,然后根据日志消息更新缓存,订阅日志目前比较流行的就是阿里开源的canal,那么我们的架构就变为如下形式。
在这里插入图片描述
订阅数据库变更日志,当数据库发生变更时,我们可以拿到具体操作的数据,然后再去根据具体的数据,去删除对应的缓存。

当然Canal 也是要配合消息队列一起来使用的,因为其Canal本身是没有数据处理能力的。
在这里插入图片描述
这个方式算的上彻底解耦了,应用程序代码无需再管消息队列方面发送失败问题,全交由 Canal来发送。

https://blog.csdn.net/m0_71777195/article/details/132017016

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

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

相关文章

vue全局组件自动注册直接使用,无需单独先引用注册再使用

目录结构: 本案例是在根目录下components文件夹测试的,文件位置项目内任意,确保在main.js挂载路径正确即可 1、新建文件夹(名字随意)zxy_components (放自己组件的地方) 2、在zxy_components文件夹下 !新建…

中科亿海微FIFO使用

引言 FPGA(现场可编程门阵列)是一种可编程逻辑器件,具有灵活性和可重构性,广泛用于数字电路设计和嵌入式系统开发。在FPGA中,FIFO(First-In, First-Out)是一种常见的存储器结构,用于…

Vue3 Props组件简单应用(父组件获取子组件数据)

去官网学习→Props | Vue.js 运行示例&#xff1a; 代码&#xff1a;App.vue <template><img alt"Vue logo" src"./assets/logo.png"><h2>Vue Props数据传递</h2><h4>子组件中的数据&#xff1a;{{ content }}</h4>…

OpenCV实例(九)基于深度学习的运动目标检测(一)YOLO运动目标检测算法

基于深度学习的运动目标检测&#xff08;一&#xff09; 1.YOLO算法检测流程2.YOLO算法网络架构3.网络训练模型3.1 训练策略3.2 代价函数的设定 2012年&#xff0c;随着深度学习技术的不断突破&#xff0c;开始兴起基于深度学习的目标检测算法的研究浪潮。 2014年&#xff0c;…

软考高项-思维导图31-33(计算机高级系统项目管理师)

陆续更新一些软考高项的思维导图&#xff0c;都是一些必背知识点&#xff0c;希望可以帮助大家早日考过高项&#xff0c;早日当上高工&#xff0c;早日成为杭州E类人才。全部完整导图快速获取链接&#xff1a;计算机高级系统项目管理师-思维导图汇总 三十一、范围确认 三十二、…

MySql(干货)

写这篇博客的目的不是为了将介绍原理&#xff0c;而是为了Sql中的代码操作属实太多了&#xff0c;在这里进行一个汇总&#xff0c;方便查阅&#xff01;&#xff01;&#xff01; Sql分类 分类全称说明 DDL Data Definintion Language数据定义语言&#xff0c;用来定义数据库对…

Linux命令200例:pwd用于显示当前工作目录的绝对路径

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

智能优化算法:白鲨优化算法-附代码

智能优化算法&#xff1a;白鲨优化算法 文章目录 智能优化算法&#xff1a;白鲨优化算法1.白鲨优化算法1.1 初始化1.2 速度更新1.3位置更新1.4鱼群行为 2.实验结果3.参考文献4.Matlab5.python 摘要&#xff1a;WSO 算法是 Braik 等于 2022 年提出一种基于白鲨深海觅食策略的新型…

第二十三章 原理篇:Pix2Seq

大夏天我好像二阳了真是要命啊。 现在找到工作了&#xff0c;感觉很快乐&#xff0c;但是也有了压力。 《论你靠吹牛混进公司后该怎么熬过试用期》 希望自己能保持学习的习惯&#xff01;加油&#xff01; 参考教程&#xff1a; https://arxiv.org/pdf/2109.10852.pdf https://…

动捕系统mockup_optitrack替换为VRPN传递信息

motive&#xff1a;启动→载入已有→layout选择capture→view选择data streming→复选marker右键create刚体→rename刚体→修改local interface为本机ip→勾选vrpn ROS端&#xff1a;roslaunch vrpn_client_ros vrpn_efy.launch 记得修改server地址为motiveip地址 关掉motive…

并查集、树状数组

并查集、树状数组、线段树 并查集树状数组树状数组1 (单点修改&#xff0c;区间查询)树状数组2 (单点查询&#xff0c;区间修改) 并查集 【模板】并查集 题目描述 如题&#xff0c;现在有一个并查集&#xff0c;你需要完成合并和查询操作。 输入格式 第一行包含两个整数 …

GIF制作器-gif动图制作助手、格式转换软件

GIF制作器​​​​​​​-无水印制作gif动态图片&#xff01; 一步轻松制作GIF&#xff0c;将视频、Live Photo、照片轻松转换成GIF动图&#xff0c;超级简单好用~ 【功能简介】 视频转GIFLive Photo转GIF图片转GIFGIF编辑 【编辑工具】 自由裁剪GIF内容调节GIF播放速度添加文…

C++ 网络编程项目fastDFS分布式文件系统(一)

目录 1.项目架构图 1.1 一些概念 1.2 项目架构图 2. 分布式文件系统 2.1 传统文件系统 2.2 分布式文件系统 3. FastDFS 3.1 fastDFS介绍 3.2 fastDFS安装 3.3 fastDFS配置文件 3.4 fastDFS的启动 4. fastDFS状态检测 4.1 对file_id的解释 4. 2上传下载代码实现 …

电流的测量(分流电流表)

在当今的大多数仪器应用中&#xff0c;可以使用两种常见的电流测量方法&#xff1a;分流电流表方法和反馈电流表方法。分流电流表方法通常与通用数字万用表 (DMM)一起使用&#xff0c;用于测量分流电阻器上的电压测量值。该电压测量结果与已知的电阻值相结合&#xff0c;得出电…

ADM2587E在RS485和RS422接口的应用(ADM2587E电路原理图和程序开发)

最近做一个项目使用到ADM2587E&#xff0c;为了解决公司历史遗留的问题&#xff08;ADM2587E芯片发烫&#xff0c;容易烧毁&#xff0c;485设备只能手拉手连接三四个&#xff0c;就通信不正常现象&#xff09;&#xff0c;认真阅读了Datasheet和官网LayOut的一些设计文档&#…

4、基于mysql实现分布式锁

目录 4.1. 基本思路4.2. 代码实现4.3 缺陷及解决方案 4.1. 基本思路 synchronized关键字和ReetrantLock锁都是独占排他锁&#xff0c;即多个线程争抢一个资源时&#xff0c;同一时刻只有一个线程可以抢占该资源&#xff0c;其他线程只能阻塞等待&#xff0c;直到占有资源的线程…

棒球和垒球的区别·棒球联盟

棒球和垒球的区别 1. 定义和起源 棒球起源于19世纪中叶的美国&#xff0c;最初被认为是一种游戏&#xff0c;而并非体育运动。那时&#xff0c;棒球常常被孩子们用来进行休闲娱乐。在20世纪初&#xff0c;它才开始被纳入体育运动的范畴。 垒球则是棒球的近亲&#xff0c;同样…

Java | 字符串

目录 一、String类 1.1 声明字符串 1.2 创建字符串 二、连接字符串 2.1 连接多个字符串 2.2 连接其他数据类型 三、获取字符串信息 3.1 获取字符串长度 3.2 字符串查找 3.3 获取指定索引位置的字符 四、字符串操作 4.1 获取字符串 4.2 去除空格 4.3 字符串替换 …

Android JNI开发从0到1,java调C,C调Java,保姆级教程详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 第一步首先配置Android studio的NDK开发环境&#xff0c;首先在Android studio中下载NDK…

QT-基于Buildroot构建系统镜像下实现QT开发

QT-基于Buildroot构建系统镜像下实现QT开发 BuildRootUboot的仓库地址和commit idKernel 的仓库地址和commit id BuildRoot已编译库在Windows上的Create上创建项目编译QT项目 BuildRoot 这部分按照100ask官网的教程走即可: Uboot的仓库地址和commit id https://e.coding.net/…