【MySQL/Redis】如何实现缓存一致
目录
不实用的方案
1. 先写 MySQL , 再写 Redis
2. 先写 Redis , 再写MySQL
3. 先删 Redis,再写 MySQL
实用的方案
1. 先删 Redis,再写 MySQL, 再删 Redis
2. 先写 MySQL , 再删 Redis
3. 先写MySQL,通过BinLog,异步更新 Redis
几种方案的对比
总结一下
在日常的工作和学习中,我们可能经常会看到关于 MySQL 和 Redis 如何才能保证缓存一致的问题,本篇文章就来带你了解一下,这到底是个什么东西。话不多说,直接开整~~
为了防止有些盆友不太了解什么是缓存一致及为什么要这么做,我先来小小的解释一下:
MySQL/Redis缓存一致性是指在使用MySQL作为持久化数据库和Redis作为缓存系统的应用场景中,确保当数据在MySQL中发生更改时,这些更改能够被及时、正确地反映到Redis的缓存中。保持缓存一致性是为了避免出现以下情况:
- 脏读:从缓存中读取到了已经过期或者已经被删除的数据。
- 不可重复读:在短时间内连续两次读取相同的数据,但得到的结果不一致。
- 幻读:因为并发操作导致读取的数据与实际存储的数据不同。
好了先来个图,大概解释一下有哪些方法(这里给出的方法只是基于M/R读写删除相关的,其它中间件或者锁等方法,等俺下次补充):
我们来一个个的看,先说这个不实用的方案,之所以说它不实用并不是否定这个方法的作用,毕竟存在即合理嘛,只是说它们在一些特定的场景中才会发挥出作用。
(注:以下图解都是以时序图解释的,相信大家都知道吧~~)
不实用的方案
1. 先写 MySQL , 再写 Redis
相信大家从图中就可以看出为什么说这是一个不推荐的方法了,两个请求都是先写MySQL,再写Redis,再并发的场景下,就可能会发生MySQL中的数据更新了,但Redis中还是旧值的情况。值得注意的一点是,再这个场景下,对于读请求,一般的处理都是先读Redis,没有的话再去数据库中查询,但是这个读请求并不会更新Redis中的数据。即,读请求不会更新Redis。
2. 先写 Redis , 再写MySQL
和前一种的方法一样,这里就不多做解释了,看图想一想就会明白。同样是在高并发的场景下会出现一些问题。
3. 先删 Redis,再写 MySQL
这里需要提醒一下,请求A是一个更新操作,请求B是一个读操作(在这个场景下,请求B是会将读到的数据回写到Redis中的)。
这个可能出现缓存不一致的处理方案,在日常开发中是经常会发生的。
因为更新操作可能耗时会比较久,所以在请求A 删除了缓存后,请求B的读请求可能会在这期间执行,而且查询操作还是很快的。剩下的看图就可以理解了。
实用的方案
1. 先删 Redis,再写 MySQL, 再删 Redis
这就是我们经常可用听到的“缓存双删”、“延迟双删除”等词。
这种方法看起来好像真的可以解决缓存不一致的问题,但是对于这个延迟删除缓存,我们到底该什么时间删呢?
以下有几个可供参考的方法:
- 定时任务: 可以在应用程序中设置一个定时任务,在第一次删除缓存后延迟指定的时间,然后执行第二次删除缓存的操作。呃,这个说白了就是让请求A 的最后一次删除缓存操作等待一段时间再去执行。
- 异步处理: 在更新数据库成功后,启动一个异步任务(例如使用 RabbitMQ、Kafka 等消息队列或者线程池),该任务在延迟指定的时间后执行第二次删除缓存的操作。
- Redis过期机制:设置Redis键的过期时间(TTL)为一个较短的值,这样即使在第二次删除之前有新请求将旧数据写入缓存,也会在短时间内自动过期并被删除。
对于第一个方法,可能是一个“好用”,但不实用的方法,这种只需要一个sleep xxx ms
,就能完成的功能,可不就是好用吗(doge)。说它不实用是因为它不可控,具体的就不说了。
然而,尽管存在这些不可控因素,定时任务仍然是实现延迟双删等策略的一种常见方法。毕竟哪有那么多的高并发的场景啊,还有就是我们程序员“懒”,功能实现了就行了
异步处理这个方法感觉是以上几个方法中,较好的,实现了功能的同时还保证了稳定。但是麻烦。。。因为需要添加一个新的组件。
下面是这个方法需要注意的几个点:
定义异步任务:首先,你需要定义一个异步任务,这个任务负责在数据库更新后执行第二次删除Redis缓存的操作。这个任务可以是一个实现了特定接口或者继承了特定类的类,具体取决于你使用的编程语言和框架。
更新数据库并触发异步任务:当需要更新数据库时,首先执行第一次删除Redis缓存的操作,然后进行数据库的更新。在数据库更新成功后,立即触发定义好的异步任务。这通常可以通过以下方式实现:
- 使用消息队列(如RabbitMQ、Kafka等):将一个消息发送到队列中,消息的内容包含了需要删除的缓存键或者其他相关的信息。然后,有一个消费者服务监听这个队列,接收到消息后执行第二次删除缓存的操作。
- 使用线程池或者异步执行框架:直接在应用程序中创建一个新的线程或者任务,将其提交到线程池或者异步执行框架中。这个新任务会在后台运行,并在延迟指定的时间后执行第二次删除缓存的操作。
实现异步任务逻辑:在异步任务的实现中,需要包含以下逻辑:
- 延迟执行:根据业务需求,设置一个合适的延迟时间。这个时间应该足够长,以覆盖从第一次删除缓存到数据库更新完成之间的时间窗口,但又不能太长,以免影响数据的一致性。
- 删除缓存:在延迟时间过后,执行第二次删除Redis缓存的操作。这通常涉及到与Redis服务器的通信,使用适当的Redis客户端库来发送删除命令。
处理异常和重试:在异步任务的执行过程中,可能会遇到各种异常情况,如网络中断、Redis服务器故障等。为了保证数据的一致性和系统的稳定性,需要实现适当的异常处理和重试机制。例如,如果删除缓存失败,可以尝试重新发送删除命令,或者记录错误日志并通知运维人员。
具体消息对列的使用,等之后再说吧。
对于第三种方法,我感觉还没第一种好,他的一下问题如下:
- 数据一致性问题:延迟双删策略的主要目的是确保在更新数据库后,能够及时删除相关的Redis缓存,以避免数据不一致。而Redis的过期机制是基于时间的,它不能精确地与数据库更新操作同步,因此可能会导致在键过期和实际删除之间的时间窗口内,新的请求查询到旧的数据库数据并将其放入缓存。
- 过期时间设置复杂性:为了使用过期机制替代延迟双删,需要为每个缓存键设置一个合适的过期时间,这个时间需要考虑到数据库更新的频率、网络延迟、系统负载等因素,设置起来可能会比较复杂和难以准确预测。
- 内存管理问题:如果大量缓存键在同一时间过期,可能会引发Redis的内存管理和垃圾回收压力,影响系统的性能和稳定性。
- 无法应对异常情况:如果在键过期和实际删除之间发生系统故障或者网络中断等情况,可能会导致数据不一致或者缓存失效的问题。
扯远了,回到正轨。。
2. 先写 MySQL , 再删 Redis
对于一些可以允许系统出现短时间不一致的场景还是可以使用的,但对于一些强一致的场景就不太适用了,比如秒杀、库存扣减等业务。
对于这个方法,还有一种特殊的场景,就是请求发出时,缓存刚好失效,可能会引发的错误。
实际上这种遇到的概率是非常小的,一是缓存刚好失效,二是请求B从数据库查到数据,且回写缓存的耗时,要比请求A写数据库且删除缓存的时间要长。这在实际开发中遇到的概率极低。
3. 先写MySQL,通过BinLog,异步更新 Redis
在这个方法中,这个查询的请求是不会回写数据到Redis中的。
对于这个方法,会保证MySQL和Redis的最终一致性,但是如果中途请求B需要查询数据,如果缓存无数据,就直接查DB;如果缓存有数据,查涧的数据也会存在不一致的情况。
所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。
几种方案的对比
1. 先写 Redis , 后写MySQL
数据没有落到数据库中你们安心吗,hxd,万一数据库宕机了,我们只靠缓存那不是开玩笑吗,正式环境中使用这种方法,就要提桶跑路了
编辑
2. 先写 MySQL, 后写 Redis
对于并发量不大、不要求强一致的使用还是挺不错的,但缺点是怕Redis突然挂了。。
3. 先删 Redis, 再写 MySQL
呃,这个的话好像没什么可以用的场景,一般都没人考虑这种吧(如有,欢迎补充!!)
4. 先删 Redis,再写 MySQL ,再删 Redis
好,但想要优雅的话,还需要支出额外的维护成本(消息队列。。)。
5. 先写 MySQL, 再删 Redis
推荐,高并发场景适用。
6. 先写 MySQL,通过 BinLog 异步更新 Redis
说使用,没试过,但这种对异地容灾、数据汇总等场景可能会较好一些。
总结一下
对于实时性要求高的,可以使用“先写 MySQL, 再删 Redis”。
对于要求强一致的,可以使用“先写 MySQL,通过 BinLog 异步更新 Redis”。
相关文章:

【MySQL/Redis】如何实现缓存一致
目录 不实用的方案 1. 先写 MySQL , 再写 Redis 2. 先写 Redis , 再写MySQL 3. 先删 Redis,再写 MySQL 实用的方案 1. 先删 Redis,再写 MySQL, 再删 Redis 2. 先写 MySQL , 再删 Redis 3. 先写MySQL,通过BinLog࿰…...
Socket.D 开源输传协议 v2.4.0 发布
Socket.D 协议 是基于"事件"和"语义消息""流"的网络应用层传输协议。有用户说,“Socket.D 之于 Socket,尤如 Vue 之于 Js、Mvc 之于 Http”。支持 tcp, udp, ws, kcp 传输。协议特点可参考《官网介绍》。 pyton 已开发完…...

单片机学习笔记---AT24C02数据存储
目录 AT24C02数据存储 准备工作 代码讲解 I2C.c 模拟起始位置的时序 模拟发送一个字节的时序 模拟接收应答的时序 模拟接收一个字节的时序 模拟发送应答的时序 模拟结束位置的时序 I2C.h AT24C02.c 字节写:在WORD ADDRESS(字地址ÿ…...

首次安装Mysql数据库
1、在mysql官网下载自己需要的版本 2、选择安装类型 3、 检查一下需求版本 4、 这里可能会弹出如下信息,先不用管这一步,点击Yes继续即可 5、 安装需要的环境,点击执行就可以,此过程会比较慢 如下就是全面安装完成了,点击next即可...
2024 前端面试题(GPT回答 + 示例代码 + 解释)No.1 - No.20
本文题目来源于全网收集,答案来源于 ChatGPT 和 博主(的小部分……) 格式:题目 h3 回答 text 参考大佬博客补充 text 示例代码 code 解释 quote 补充 quote 目录 No.1 - No.20 本文题目来源于全网收集,答案来源于…...
通过`ssh`同步`tmux`剪贴板内容
通过ssh同步tmux剪贴板内容 通过ssh连接远程服务器时,可以通过xclip同步tmux剪贴板内容。这需要在服务器上安装xclip,且需要在ssh远程连接时开启X11。 此处附tmux剪贴板调用xclip的配置: # Copy the current buffer to the system clipboa…...
HTTP 响应状态代码
HTTP 响应状态代码 HTTP 响应状态代码指示特定 HTTP 请求是否已成功完成。 响应分为五类: 信息性回复 ( 100 – 199)成功响应 ( 200 – 299)重定向消息 ( 300 – 399)客户端错误响应 ( 400 – 499)服务器错误…...

[OPEN SQL] 新增数据
INSERT语句用于数据的新增操作 本次操作使用的数据库表为SCUSTOM,其字段内容如下所示 航班用户(SCUSTOM) 该数据库表中的部分值如下所示 1.插入单条数据 语法格式 INSERT <dbtab> FROM <wa>. INSERT INTO <dbtab> VALUES <wa>. INSERT &…...

OpenHarmony—UIAbility组件生命周期
概述 当用户打开、切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调,通过这些回调可以知道当前UIAbility实例的某个状态发生改变,会经过UIAbility实例的创建和销毁,…...
Mybatis的使用
MyBatis 是一个流行的 Java 持久层框架,它提供了 SQL 映射和对象关系映射的功能,让开发者能够更加便捷地操作数据库。MyBatis 通过 XML 或注解的方式配置 SQL 语句,并将 Java 对象与数据库表进行映射,以简化 JDBC 的复杂操作。以下…...
Python 播放音乐
本篇是使用Python pygame库来实现操作音乐。 安装pygame 播放音乐需要pygame库,如果没有可以进行安装。 命令如下: pip install pygame 引入类库 需要引入两个类库,即time和pygame。 示例如下: import time import pygame 播…...
[嵌入式系统-21]:RT-Thread -7- 内核组件编程接口 - 定时器
目录 一、RT-Thread定时器 1.1 概述 1.2 定时器的种类 1.2.1 周期性 1.2.2 实时性 1.2.3 功能 二、 RT-Thread 定时器的一般步骤 2.1 步骤 2.2 Flag 2.3 示例 一、RT-Thread定时器 1.1 概述 在 RT-Thread 中,定时器是一种常用的机制,用于在指…...

Python Matplotlib 的学习笔记
Python Matplotlib 的学习笔记 0. Python Matplotlib 简介1. 为什么要用 Matplotlib?2. Matplotlib 基础类详解2-1. Line(线)2-2. Marker(标记)2-3. Text(文本)2-4. Legend(图例&…...
SQL语言1
创建数据库 CREATE DATABASE 展示数据库 SHOW DATABASE 整数 INT 有小数点的数 DECIMA(m, n) #m是有几位数,n是有几位小数 字符串 VARCHAR(n) (Binary Large Object)图片 影片 BLOB ‘YYYY-MM-DD’日期 DATA YYYY-MM-DD HH:MM:SS 记…...

PowerShell搭建vue起始项目
Windows PowerShell搭建vue起始项目 搜索PowerShell,以管理员身份运行。 复制文件夹路径 cd 到这个文件夹位置 命令行创建项目:vue create 项目名 这里写自己的项目名就行,我写的yeb vue create yeb 创建成功后是这样的 有颜色的就是选中的ÿ…...

jmeter遇到连接数据库的问题
jmeter连接mysql或者oracle简单,但是连接过inceptor吗? 上货 1、下载驱动inceptor 5.1.2.jar包 2、在添加驱动那里导入 3、在JBC request中的写法 PS:没什么可说的...

应急响应实战笔记02日志分析篇(3)
第3篇:Web日志分析 ox01 Web日志 Web访问日志记录了Web服务器接收处理请求及运行时错误等各种原始信息。通过对WEB日志进行的安全分析,不仅可以帮助我们定位攻击者,还可以帮助我们还原攻击路径,找到网站存在的安全漏洞并进行修复。 我们来…...
常见性能优化策略
对于经常接触高并发服务的同学来学,会经常涉及到性能优化,但是由于平时很少总结,内容会比较分散,这里简单做一些总结 1:空间换时间 比如一些数据的访问需要很快返回结果,原本在磁盘上的数据,需…...
【微信小程序】微信小程序开发:从入门到精通
微信小程序开发:从入门到精通 一、开发准备二、小程序开发流程1、注册与创建项目2、开发页面3、配置4、调试与预览5、发布与审核 随着移动互联网的普及,微信小程序成为了越来越多企业和个人开发者的首选。小程序是一种无需下载安装即可使用的应用&#x…...
【经验】STM32的一些细节
这两天 碰到的奇葩问题是 STM32定时器同步的问题。 我的设计本意是:使用定时器T3以100us的周期来定时发送命令给 FPGA。由于编码器出结果的最长时间为51us。因此,希望PWM中断要滞后于T3 约60us 。 调试过程:分别在T3和PWM中断中置IO1&#…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...