Redis 数据一致性方案的分析与研究
点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~
一般的业务场景都是读多写少的,当客户端的请求太多,对数据库的压力越来越大,引入缓存来降低数据库的压力是必然选择,目前业内主流的选择基本是使用 Redis 作为数据库的缓存。但是引入缓存以后,对我们系统的设计带来了很大的挑战,其中缓存和数据库的数据一致性问题就是一个非常棘手的问题,今天我们就来聊一聊在项目中,我们用什么方案来解决数据一致性的问题。
1
Redis 的使用场景

我们在实际项目中,通常情况下的应用是读多写少的,我们一般用 Redis 来解决读数据库的压力问题,也就是说用 Redis 作为缓存来减轻由于客户端频繁查询数据库而对数据库造成的压力。
因此在项目中由客户端直接查询数据库返回数据,变成了客户端查询 Redis 缓存,缓存有数据则直接返回数据,缓存没有数据再去查询数据库,同时数据库的数据写入到 Redis 缓存。

在以上的使用场景中,我们一般缓存热点数据,比如电商平台中的热销商品,用户的登录信息,新闻类平台中的热点新闻等。
2
数据一致性问题产生的原因

当数据库的数据发生变化的时候,引入 Redis 缓存以后,增加了数据操作难度,既要操作数据库又要操作 Redis,对 Redis和数据库的操作有 2 种方案:
1、先操作 Redis,再操作数据库
2、先操作数据库,再操作 Redis
我们无论选择以上的哪个方案,都希望数据操作要么都成功,要么都失败,我们不希望看到一个失败,一个成功的结果,因为这样就产生了数据不一致的问题。
举个例子说明:
假设 Redis 里缓存了一个热点商品数据,有个 key 为 1001 的商品名称为“华为手机”,数据库里这个 1001 号的商品名称也是华为手机;此时商家觉得商品名称叫华为手机有点宽泛,需要精确一下,把 1001 号商品的名称修改为其对应的具体名称“华为 P40 Pro”。
1、如果我们选择先操作 Redis,再操作数据库的方案,当操作 Redis 成功,操作数据库失败的时候,Redis 里的名称修改为“华为 P40 Pro”,但是数据库的名称还是“华为手机”,产生了数据不一致问题。
2、如果我们选择先操作数据库,再操作 Redis 的方案,当操作数据库成功,操作 Redis 失败的时候,数据库里的名称修改为“华为 P40 Pro”,但是 Redis 的名称还是“华为手机”,产生了数据不一致问题。
由于 Redis 和数据库是 2 个不同的中间件,我们无法通过事务来很好地解决数据一致性的问题,因此只能在数据实时一致性和系统性能上做权衡,选择一个可以接收的方案。
因为数据库是稳定的持久化的系统,比 Redis可靠,我们一般都是以数据库的数据为准,解决这个数据一致性问题的原则就是:我们可以为 Redis 缓存数据设置一个过期的时间,当 Redis 数据过期了就去数据库查询,然后再把数据库的数据写入 Redis 缓存中,确保数据的一致性。
如果在这个过期时间范围内,数据发生了更新操作,当更新操作有一个失败,有一个成功,就会产生不一致的问题,这个过期时间设置的太短了,数据库的压力还是很大,过期时间设置的太长了,不一致性的问题就会凸显,因此我们需要基于以数据库的数据为准的原则下,继续探讨数据一致性问题的解决方案。
3
数据更新时,如何操作 Redis

当数据更新时,操作数据库我们都很熟悉,直接更新数据库的值就可以了,但是操作 Redis,我们应该怎么办?
1、有人会说,这还不简单,数据变化时,直接更新缓存
2、也有人会说数据变化时把缓存的数据删掉,当查询请求发现缓存没有数据,就会从数据库加载新的数据。
以上的两种方法都可以,那我们该如何选择呢?
如果我们选择更新 Redis 的方案,我们要评估一下更新的代价大不大,比如拿到数据更新到 Redis 需要经过很多表的关联查询,或者多个接口的调用查询,经过大量计算才能得到数据的话,就不要使用更新 Redis 的方案了,因为面对这么复杂的更新流程,不如直接删掉方便。如果要更新的数据直接可以获取到,那么选择更新 Redis 的方案就是可行的。
直接删除 Redis 缓存数据是更加简单的方案,因为缓存数据删了,只能从数据库读取了,这样可以避免不一致性,互联网公司一般选择删除 Redis 的方案。
下面我们用一张图来表示可选的方案:

通过上图,我们可以看出当数据发生变化时,Redis 与数据库的处理流程我们可以从 Redis更新还是删除这 2 个方面分析,然后再从先操作 Redis还是先操作数据库这 2 个方面分析,下面我们按此思路继续深入分析。
4
选择 Redis 更新方案

1、先更新 Redis,再更新数据库
首先不推荐选择这种方案。
因为我们的数据一致性的基本原则是以数据库的数据为准,所以先更新缓存的话,会存在缓存更新成功,数据库更新失败的情况,此时面临的问题就需要回滚掉刚才更新缓存成功的操作,那么就需要从业务代码里加入很多判断逻辑来处理这种异常,需要考虑数据变化是 insert,update,delete 等,根据不同的场景执行不同的回滚方案,这种回滚操作比较麻烦,对我们业务代码的倾入性也比较大,所以不推荐选择这种方案。
当然,如果业务层面可以接受这种数据的不一致性,异常情况就不需要考虑,选择这种方案也是可以的。
2、先更新数据库,再更新 Redis
先更新数据库,再更新 Redis 的方案,由于把数据库操作放到了前面,如果数据库操作失败,那么客户端再发起一次就可以了。
如果数据库操作成功,Redis 操作失败,此时由于数据库的数据已经设置成功,我们的重点就是讨论如何把 Redis的数据操作成功即可。从客户端层面来看,他提交的数据已经在我们系统里存在了,此时的问题就是我们系统内部如何解决的问题。
对于 Redis 操作失败,我们可选的方案有以下几个,根据业务要求的数据一致性级别,进行权衡选择即可。
1、不做任何操作,等着Redis里的缓存数据过期后,自动从数据库同步最新的数据,此时最严重的数据不一致性周期就是在缓存过期的一段时间(考虑一下这个过期时间的范围);如果在这个时间段内,又有新的更新请求,也许这次就更新缓存成功了。
2、如果数据一致性要求比较高,那么 Redis 操作失败后,我们把这个操作记录下来,异步处理,用 Redis 的数据去和数据库比对,如果不一致,再次更新缓存确保缓存数据与数据库数据一致。
5
选择 Redis 删除方案

由于目前数据发生变化,主流的方案是选择删除 Redis,所以我们对这个方案重点进行分析。
1、先更新数据库,再删除 Redis
我们如果更新数据库成功,删除 Redis 失败,那么 Redis 里存放的就是一个旧值,也就是删除缓存失败导致缓存和数据库的数据不一致了。
对于删除 Redis 失败的异常情况来进行分析,我们一般有如下的方案:
1、我们可以重试删除,比如:我们可以把删除动作发送到消息队列 MQ,MQ的消费者再去删除这个key,一致尝试删除操作,确保缓存删除成功,这个方案缺陷就是删除缓存的地方,通过代码实现对业务逻辑产生了入侵。
2、另外一种方案,就是完全异步的方案,对业务逻辑没有入侵,通过监听 binlog 的变化来删除缓存。我们更新数据库会产生 binlog,我们可以用一个服务监听数据库 binlog 的变化,异步去删除缓存,阿里开源的 canal 就是可以监听 binlog 的工具,只要数据发生变化就可以删除 Redis 的数据。
2、先删除 Redis,再更新数据库
我们如果删除 Redis 缓存成功,更新数据库失败的话,因为我们是以数据库为准,再查一次就可以了,这个方案看似很理想,感觉没什么问题,其实在并发下也可能产生数据一致性问题。
我们先看一个并发环境下的更新和查询流程:

上面的图表示,Thread-1 是个更新流程,Thread-2 是个查询流程,cpu 执行顺序是:Thread-1 删除缓存成功,此时 Thread-2 获取到 CPU 执行查询缓存没有数据,然后查询数据库把数据库的值写入缓存,因为此时 Thread-1 更新数据库还没有执行,所以缓存里的值是一个旧值(old),最后 CPU 执行 Thread-1 更新数据库成功的代码,那么此时数据库的值是新增(new),这样就产生了数据不一致行的问题。
要解决以上的问题,我们我们一般有如下的可选方案:
1、并发下多线程操作能不能让同一个操作里的 2 个步骤同时操作完成,下一个线程才能执行,让各个线程排队操作,有人说加锁,如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是数据库本来是并发的,现在变成串行的了,所以这个方案看起来不太可行,加锁或者排队执行的方案降低了系统性能。
2、另外一种放啊就是延时双删的方案,也就是先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。
伪代码如下:
redes.del(key);
db.update(data);
Thread.sleep(2000);
redes.del(key); 以上谈到的都是一些解决方案,没有说哪个方案更好,或者哪个方案不行,以上的方案都是基于实时一致性和系统性能的情况下进行权衡选择,根据不同的业务,不同的数据一致性要求,结合系统的性能综合考虑,选择适合自己系统的方案就好。
总的来说,由于我们的基本原则是以数据库为准,那么我们选择的方案就应该把操作数据库放到前面,也就是说我们应该先操作数据库,再操作 Redis,对于并发很高的场景,我们可以在操作数据库之前通过消息队列来降低客户端对数据库的请求压力。
相关文章:
Redis 数据一致性方案的分析与研究
点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~ 一般的业务场景都是读多写少的,当客户端的请求太多,对数据库的压力越来越大,引入缓存来降低数据库的压力是必然选择,目前业内…...
【网络安全】黑客自学笔记
1️⃣前言 🚀作为一个合格的网络安全工程师,应该做到攻守兼备,毕竟知己知彼,才能百战百胜。 计算机各领域的知识水平决定你渗透水平的上限🚀 【1】比如:你编程水平高,那你在代码审计的时候就会比…...
深入解析Perlin Simplex噪声函数:在C++中构建现代、高效、免费的3D图形背景
引言 在计算机图形中,噪声是一个经常被讨论的话题。无论是为了制造自然的纹理,还是为了模拟复杂的现实世界现象,噪声函数都在其中起着关键作用。而在众多噪声函数中,Perlin Simplex 噪声无疑是最受欢迎的一种。其原因不仅在于其干…...
【计算机辅助蛋白质结构分析、分子对接、片段药物设计技术与应用】
第一天 上午 生物分子互作基础 1.生物分子相互作用研究方法 1.1蛋白-小分子、蛋白-蛋白相互作用原理 1.2 分子对接研究生物分子相互作用 1.3 蛋白蛋白对接研究分子相互作用 蛋白数据库 1. PDB 数据库介绍 1.1 PDB蛋白数据库功能 1.2 PDB蛋白数据可获取资源 1.3 PDB蛋白数据库对…...
免费开箱即用微鳄售后工单管理系统
编者按:本文介绍基于天翎MyApps低代码平台开发的微鳄售后工单管理系统, 引入低代码平台可以帮助企业快速搭建和部署售后工单管理系统, 以工作流作为支撑,在线完成各环节数据审批,解决售后 工单 服务的全生命周期过程管…...
vant 组件库的基本使用
文章目录 vant组件库1、什么是组件库2、vant组件 全部导入 和 按需导入的区别3、全部导入的使用步骤:4、按需导入的使用步骤:5、封装vant文件包 vant组件库 该项目将使用到vant-ui组件库,这里的目标就是认识他,铺垫知识 1、什么…...
HTML常用基本元素总结
<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title> biao qian</title> </head> <body><h1>这是标题1</h1> <h2>这是标题2</h2> <h3>这是标题3</h3><p> 这…...
msvcp140.dll重新安装的解决方法是什么?(最新方法)
msvcp140.dll 是 Microsoft Visual C Redistributable 的一个动态链接库文件,它包含了 C 运行时库的一些函数和类,对于许多应用程序和游戏来说都是必需的。如果您的系统中缺失了这个文件,可能会导致程序无法正常运行。下面我们将分享修复 msv…...
USI-0002 SDI-1624 HONEYWELL ,用于工业和物流4.0的人工智能
USI-0002 SDI-1624 HONEYWELL ,用于工业和物流4.0的人工智能 生产、仓库、运输——生产、储存、分拣或包装货物的地方,也是提货的地方。这意味着几个单独的货物从存储单元如箱子或纸盒中取出并重新组装。有了FLAIROP(机器人采摘的联邦学习)项目费斯托…...
计算机竞赛 深度学习 python opencv 火焰检测识别
文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数:3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…...
Intellij idea 2023 年下载、安装教程、亲测可用
文章目录 1 下载与安装IDEA2 常用设置设置 Java JDK 版本自动导入包、移除包IDEA 自动生成 author 注释签名java.io.File 类无法自动提示导入?高亮显示与选中字符串相同的内容IDEA 配置 MavenIDEA 连接 Mysql 数据库 3 参考文章 1 下载与安装IDEA 首先先到官网下载…...
AI文本创作在百度App发文的实践
作者 | 内容生态端团队 导读 大语言模型(LLM)指包含数百亿(或更多)参数的语言模型,这些模型通常在大规模数据集上进行训练,以提高其性能和泛化能力。在内容创作工具接入文心一言AI能力后,可以为…...
Kafka 集群与可靠性
文章目录 Kafka集群的目标Kafka集群规模如何预估Kafka集群搭建实战Kafka集群原理成员关系与控制器集群工作机制replication-factor参数auto.leader.rebalance.enable参数 集群消息生产可靠的生产者ISR(In-sync Replicas)使用ISR方案的原因ISR相关配置说明…...
【刷题】蓝桥杯
蓝桥杯2023年第十四届省赛真题-平方差 - C语言网 (dotcpp.com) 初步想法,x y2 − z2(yz)(y-z) 即xa*b,ayz,by-z 2yab 即ab是2的倍数就好了。 即x存在两个因数之和为偶数就能满足条件。 但时间是(r-l)*x&am…...
C++入门及简单例子_4
1. 类和对象: #include <iostream> // 包含输入输出流库的头文件class Rectangle { // 定义名为Rectangle的类 private: // 私有成员变量部分double length; // 长度double width; // 宽度public: // 公有成员函数部分Rectangle(double len, double w…...
成集云 | 用友U8集成聚水潭ERP(用友U8主管库存)| 解决方案
源系统成集云目标系统 方案介绍 用友U8是一套企业级的解决方案,可满足不同的制造、商务模式下,不同运营模式下的企业经营管理。它全面集成了财务、生产制造及供应链的成熟应用,并延伸客户管理至客户关系管理(CRM)&am…...
提升网站效率与SEO优化:ZBlog插件集成解决方案
在创建和管理网站的过程中,使用合适的工具和插件可以大幅提升效率,并改善搜索引擎优化(SEO)结果。ZBlog插件是为ZBlogCMS设计的一组工具,它们帮助网站管理员轻松地满足各种需求,从采集内容到发布、推送和SE…...
C语言的编译过程详解
当我们编译C程序时会发生什么?编译过程中的组件有哪些,编译执行过程是什么样的? 什么是编译 C语言的编译过程就是把我们可以理解的高级语言代码转换为计算机可以理解的机器代码的过程,其实就是一个翻译的过程。 …...
无人机航测没信号?北斗卫星来解决
无人机航测是利用无人机进行地理信息的采集和处理的航测方式。相比传统的航测手段,无人机航测具备更高的灵活性、更低的成本和更广阔的适应性。无人机航测可以应用于土地测绘、农业植保、城市规划、自然资源调查等多个领域,极大地提高了测绘的效率和准确…...
Vue 03 数据绑定
Vue中有2种数据绑定的方式: 1.单向绑定(v-bind): 数据只能从data流向页面。 2.双向绑定(v-model): 数据不仅能从data流向页面,还可以从页面流向data。 备注: 1.双向绑定一般都应用在表单类元素上(如&am…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
