【Redis】如何保证Redis缓存与数据库的一致性?
文章目录
- 1、四种同步策略
- 2、更新缓存还是删除缓存
- 2.1 更新缓存
- 2.2 删除缓存
- 3、先操作数据库还是缓存
- 3.1 先删除缓存再更新数据库
- 3.2 先更新数据库再删除缓存
- 4、延时双删
- 4.1 采用读写分离的架构怎么办?
- 5、利用消息队列进行删除的补偿
1、四种同步策略
想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:
- 先更新缓存,再更新数据库;
- 先更新数据库,再更新缓存;
- 先删除缓存,再更新数据库;
- 先更新数据库,再删除缓存。
从这4种同步策略中,我们需要作出比较的是:
更新缓存与删除缓存哪种方式更合适?应该先操作数据库还是先操作缓存?
2、更新缓存还是删除缓存
下面,我们来分析一下,应该采用更新缓存还是删除缓存的方式。
2.1 更新缓存
-
优点:每次数据变化都及时更新缓存,所以查询时不容易出现未命中的情况。
-
缺点:更新缓存的消耗比较大。如果数据需要经过复杂的计算再写入缓存,那么频繁的更新缓存,就会影响服务器的性能。如果是写入数据频繁的业务场景,那么可能频繁的更新缓存时,却没有业务读取该数据。
2.2 删除缓存
-
优点:操作简单,无论更新操作是否复杂,都是将缓存中的数据直接删除。
-
缺点:删除缓存后,下一次查询缓存会出现未命中,这时需要重新读取一次数据库。从上面的比较来看,一般情况下,删除缓存是更优的方案。
3、先操作数据库还是缓存
下面,我们再来分析一下,应该先操作数据库还是先操作缓存。
首先,我们将先删除缓存与先更新数据库,在出现失败时进行一个对比:
3.1 先删除缓存再更新数据库

如上图,是先删除缓存再更新数据库,在出现失败时可能出现的问题:
- 线程A删除缓存成功,线程A更新数据库失败;
- 线程B从缓存中读取数据;由于缓存被删,进程B无法从缓存中得到数据,进而从数据库读取数据;此时数据库中的数据更新失败,线程B从数据库成功获取旧的数据,然后将数据更新到了缓存。
- 最终,缓存和数据库的数据是一致的,但仍然是旧的数据
3.2 先更新数据库再删除缓存

如上图,是先更新数据库再删除缓存,在出现失败时可能出现的问题:
- 线程A更新数据库成功,线程A删除缓存失败;
- 线程B读取缓存成功,由于缓存删除失败,所以线程B读取到的是缓存中旧的数据。
- 最后线程A删除缓存成功,有别的线程访问缓存同样的数据,与数据库中的数据是一样。
- 最终,缓存和数据库的数据是一致的,但是会有一些线程读到旧的数据。
经过上面的比较,我们发现在出现失败的时候,是无法明确分辨出先删缓存和先更新数据库哪个方式更好,以为它们都存在问题。后面我们会进一步对这两种方式进行比较,但是在这里我们先探讨一下,上述场景出现的问题,应该如何解决呢?
实际上,无论上面我们采用哪种方式去同步缓存与数据库,在第二步出现失败的时候,都建议采用重试机制解决,上面两幅图中已经画了。
下面我们再将先删缓存与先更新数据库,在没有出现失败时进行对比:

如上图,是先删除缓存再更新数据库,在没有出现失败时可能出现的问题:
- 线程A删除缓存成功;
- 线程B读取缓存失败;
- 线程B读取数据库成功,得到旧的数据;
- 线程B将旧的数据成功地更新到了缓存;
- 线程A将新的数据成功地更新到数据库。
可见,进程A的两步操作均成功,但由于存在并发,在这两步之间,进程B访问了缓存。最终结果是,缓存中存储了旧的数据,而数据库中存储了新的数据,二者数据不一致。

如上图,是先更新数据库再删除缓存,在没有出现失败时可能出现的问题:
- 线程A更新数据库成功;
- 线程B读取缓存成功;
- 线程A删除缓存成功。
可见,最终缓存与数据库的数据是一致的,并且都是最新的数据。但线程B在这个过程里读到了旧的数据,可能还有其他线程也像线程B一样,在这两步之间读到了缓存中旧的数据,但因为这两步的执行速度会比较快,所以影响不大。对于这两步之后,其他进程再读取缓存数据的时候,就不会出现类似于进程B的问题了。
最终结论:
经过对比你会发现,先更新数据库、再删除缓存是影响更小的方案。如果第二步出现失败的情况,则可以采用重试机制解决问题。
4、延时双删
上面我们提到,如果是先删缓存、再更新数据库,在没有出现失败时可能会导致数据的不一致。如果在实际的应用中,出于某些考虑我们需要选择这种方式,那有办法解决这个问题吗?答案是有的,那就是采用延时双删的策略,延时双删的基本思路如下:
- 删除缓存;
- 更新数据库;
- sleep N毫秒;
- 再次删除缓存。
public void write(String key, Object data) {Redis.delKey(key);db.updateData(data);Thread.sleep(1000);Redis.delKey(key);
}
阻塞一段时间之后,再次删除缓存,就可以把这个过程中缓存中不一致的数据删除掉。而具体的时间,要评估你这项业务的大致时间,按照这个时间来设定即可。
4.1 采用读写分离的架构怎么办?
如果数据库采用的是读写分离的架构,那么又会出现新的问题,如下图:

此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
- 请求 A 更新操作,删除了 Redis;
- 请求主库进⾏更新操作,主库与从库进行同步数据的操作;
- 请 B 查询操作,发现 Redis 中没有数据;
- 去从库中拿去数据;
- 此时同步数据还未完成,拿到的数据是旧数据;
此时的解决办法就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进⾏查询。
删除失败了怎么办?
如果删除依然失败,则可以增加重试的次数,但是这个次数要有限制,当超出一定的次数时,要采取报错、记日志、发邮件提醒等措施。
5、利用消息队列进行删除的补偿
先更新数据库,后删除缓存这⼀种情况也会出现问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。

此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑⽤语⾔描述如下:
- 请求 线程A 先对数据库进行更新操作;
- 在对 Redis 进行删除操作的时候发现报错,删除失败;
- 此时将Redis 的 key 作为消息体发送到消息队列中;
- 系统接收到消息队列发送的消息后再次对 Redis 进行删除操作;
但是这个方案会有⼀个缺点就是会对业务代码造成大量的侵入,深深的耦合在⼀起,所以这时会有⼀个优化的方法,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。

相关文章:
【Redis】如何保证Redis缓存与数据库的一致性?
文章目录 1、四种同步策略2、更新缓存还是删除缓存2.1 更新缓存2.2 删除缓存 3、先操作数据库还是缓存3.1 先删除缓存再更新数据库3.2 先更新数据库再删除缓存 4、延时双删4.1 采用读写分离的架构怎么办? 5、利用消息队列进行删除的补偿 1、四种同步策略 想要保证缓…...
MATLAB中ischange函数用法
目录 语法 说明 示例 均值的变化 线性区的变化 矩阵数据 ischange函数的功能是查找数据中的突然变化。 语法 TF ischange(A) TF ischange(A,method) TF ischange(___,dim) TF ischange(___,Name,Value) [TF,S1] ischange(___) [TF,S1,S2] ischange(___) 说明 …...
【React + Ant Design】表单如何在前置项未填写时禁止后置项交互并提示
在 react antd 中,对表单做在前置项未填写时禁用后置项交互并提示的效果。 情景 最近有这么个需求,某个业务中,要填写一张表单,其中有这样两项:选择数据连接和选择数据表,数据表是数据连接下所拥有的表。…...
Linux学习之MySQL建表
MySQL查询1 MySQL查询2 表管理 #1. 建库#1)库名命名规则仅可以使用数字、字母、下划线、不能纯数字,区分字母大小写,具有唯一性,不可使用MySQL命令或特殊字符#创建数据表时可以查看一下默认的字符集,8.0后创建数据库…...
Redis哨兵集群的介绍及搭建
Redis 是一款开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。然而,作为一个单点服务,Redis 在面临硬件故障或者网络问题时可能会导致服务不可用。为了解决这个问题,Redis 提供了哨兵模式,一个…...
【zookeeper】zookeeper日常运维
本文将分享一些zookeeper在日常使用中一些维护经验。 zookeeper清理快照 脚本或者命令清理 zookeeper长时间运行,快照逐渐增多可能造成服务器磁盘被占满的情况,但我们不能贸然用rm命令删除快照文件,如果直接删完会导致丢失好多数据&#x…...
【工作记录】MQTT介绍、安装部署及springboot集成@20230912
背景 近期公司可能会有物联网设备相关项目内容,提前对用到的mqtt协议做预研和初步使用。 最初接触到mqtt协议应该是早些年的即时通讯吧,现在已经是物联网设备最热门的协议了。 作为记录,也希望能帮助到需要的朋友。 MQTT介绍 《MQTT 协议规…...
Flask 使用 JWT(一)
下面是一些 JWT 的使用场景: 1、 授权:这是 JWT 最常的使用场景。一旦用户登录,后续的每个请求都必须携带 JWT ,允许用户携带 Token 访问所有的路由、服务器和资源。单点登录时目前使用最广泛的一个场景,因为它开销小并且能够轻易的实现跨域访问。 2、信息交换:JWT Token…...
Oracle(1):Oracle简介
1 什么是 ORACLE ORACLE 数据库系统是美国 ORACLE 公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器(CLIENT/SERVER)或B/S 体系结构的数据库之一。 ORACLE 通常应用于大型系统的数据库产品。 ORACLE 数据…...
计算机网络篇之IP地址
计算机网络篇之IP地址 文章目录 计算机网络篇之IP地址概括IPv4地址IPv6地址分配总结 概括 IP地址是计算机网络中用于标识和定位设备的一组数字,IP地址分为IPv4和IPv6两种格式 IPv4地址 IPv4地址是32位的二进制数,通常表示为四个用点分隔的十进制数&am…...
webrtc-m79-测试peerconnectionserver的webclient-p2p-demo
1 背景 webrtc的代码中有peerconnectionclient和peerconnectionserver的例子,但是没有对应的web端的例子,这里简单的写了一个测试例子,具体如下: 2 具体操作 2.1 操作流程 2.2 测试效果 使用webclient与peerconnectionclient的…...
C#,《小白学程序》第十五课:随机数(Random)第二,统计学初步,数据统计的计算方法与代码
1 文本格式 /// <summary> /// 《小白学程序》第十五课:随机数(Random)第二,统计学初步,数据统计的计算方法与代码 /// 用随机数做简单的统计并用图形显示统计结果。 /// </summary> /// <param name&q…...
C# 子类如何访问子类的方法(同一父类)
在继承关系中,子类可以通过创建另一个子类的对象来访问其方法。下面是一个示例,展示了子类如何访问另一个子类的方法: public class Animal {public virtual void Speak(){Console.WriteLine("我是动物。");} }public class Cat :…...
《Docker 容器化的艺术:深入理解容器技术》
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: 📚…...
gitlab配置hook,commit message的时候校验提交的信息
在 GitLab 中配置 Webhook 来调用 Java 接口以校验 commit 信息,是很多公司的一些要求,因为提交信息的规范化是必要的 不阻止commit的版本 在 GitLab 项目中进入设置页面。 在左侧导航栏中选择 “Webhooks”(Web钩子)。 在 We…...
ssh远程管理服务
ssh远程管理服务是什么 SSH是一个安全协议,在进行数据传输时,会对数据包进行加密处理,加密后在进行数据传输。确保了数据传输安全, 那SSH服务主要功能有哪些呢? 1.提供远程连接服务器的服务 1)linux远程连接协议&…...
C语言顺序表
文章目录 前言线性表顺序表静态顺序表动态顺序表 接口实现 前言 我们先补一下上篇博客落下的知识点: 首先说一下斐波那契的时间复杂度和空间复杂度: long long Fac(size_t N) {if(0 N)return 1;return Fac(N-1)*N; }还是说一下size_t代表的类型是unsi…...
滑动窗口详解
滑动窗口本质其实也是一种双指针算法,只是因为它维护的区间随着遍历的进行在不停变化,所以形象地称为“滑动窗口” 一、⻓度最⼩的⼦数组 题目要求找到满足条件的长度最小的子数组,我们先来想想暴力的做法,再来想想能不能优化&am…...
JAVA -华为真题-分奖金
需求: 公司老板做了一笔大生意,想要给每位员工分配一些奖金,想通过游戏的方式来决定每个人分多少钱。按照员工的工号顺序,每个人随机抽取一个数字。按照工号的顺序往后排列,遇到第一个数字比自己数字大的,那么…...
第二章:25+ Python 数据操作教程(第十八节如何使用 Matplotlib 库在 python 中执行绘图和数据可视化)持续更新中
本教程概述了如何使用 Matplotlib 库在 python 中执行绘图和数据可视化。这篇文章的目的是让您熟悉该库的基础知识和高级绘图功能。它包含几个示例,将为您提供使用 Python 生成绘图的实践经验。 目录 什么是 Matplotlib? Matplotlib 基础知识<...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
