如何保证Redis与MySQL双写一致性

什么是双写一致性问题?
双写一致性主要指在一个数据同时存在于缓存(如Redis)和持久化存储(如MySQL)的情况下,任何一方的数据更新都必须确保另一方数据的同步更新,以保持双方数据的一致状态。这一问题的核心在于如何在高并发环境下正确处理缓存与数据库的读写交互,防止数据出现不一致的情况。

一致性通常可以分为以下几个类别:

强一致性:
所有节点在任何时间都看到相同的数据。任何更新操作都会立即对所有节点可见,保证了数据的强一致性。这意味着,如果一个节点完成了写操作,那么所有其他节点读取相同的数据之后,都将看到最新的结果。强一致性通常需要付出更高的代价,例如增加通信开销和降低系统的可用性。

弱一致性:
系统中的数据在某些情况下可能会出现不一致的状态,但最终会收敛到一致状态。弱一致性下的系统允许在一段时间内,不同节点之间看到不同的数据状态。弱一致性通常用于需要在性能和一致性之间进行权衡的场景,例如缓存系统等。

最终一致性:
是弱一致性的一种特例,它保证了在经过一段时间后,系统中的所有节点最终都会达到一致状态。尽管在数据更新时可能会出现一段时间的不一致,但最终数据会收敛到一致状态。最终一致性通常通过一些技术手段来实现,例如基于版本向量或时间戳的数据复制和同步机制。

1、缓存常见读取数据、写数据用法

2、缓存不一致产生的原因
如果数据一直没有变更,那么就不会出现Redis和MySQL数据不一致性的问题。

两者之间数据不一致是因为一者发生了数据的变更,另一者如何在短时间内同步数据的问题。因为每次数据变更需要同时操作数据库和缓存,而他们又属于不同的系统,无法做到同时操作成功或失败,总会有一个时间差。在并发读写的时候可能就会出现缓存不一致的问题。
 

保证数据一致性通常涉及5种策略

1、先更新数据库,再更新缓存

@Transactional
public void updateUser(User user) {// 1. 更新数据库userMapper.updateUser(user);// 2. 更新Redis缓存// 方式1:更新缓存redisTemplate.opsForValue().set("user:" + user.getId(), user);// 方式2:删除缓存(推荐)redisTemplate.delete("user:" + user.getId());
}

如上图所示,其可能执行的流程顺序为:
1.客户端1 触发更新数据A的逻辑
2.客户端2 触发查询数据A的逻辑
3.客户端3 触发查询数据A的逻辑
4.客户端1 更新数据库中数据A
5.客户端2 查询缓存中数据A,命中返回(旧数据)
6.客户端1 让缓存中数据A失效
7.客户端3 查询缓存中数据A,未命中
8.客户端3 查询数据库中数据A,并更新到缓存中
可见,最后缓存中的数据A和数据库中的数据A是一致的,理论上可能会出现一小段时间数据不一致,不过这种概率也比较低,大部分的业务也不会有太大的问题。

为什么操作缓存的时候是删除旧缓存而不是直接更新缓存?

举个例子:

线程A先发起一个写操作,第一步先更新数据库,然后更新缓存
线程B再发起一个写操作,第二步更新了数据库,然后更新缓存
当以上两个线程的执行,如果严格先后顺序执行,那么对于更新缓存还是删除缓存去操作缓存都可以,但是如果两个线程同时执行时,由于网络或者其他原因,导致线程B先执行完更新缓存,然后线程A才会更新缓存。这时候缓存中保存的就是线程A的数据,而数据库中保存的是线程B的数据。这时候如果读取到的缓存就是脏数据。但是如果使用删除缓存取代更新缓存,那么就不会出现这个脏数据。

2、先更新缓存,再更新数据库

@Transactional
public void updateUser(User user) {// 1. 删除Redis缓存redisTemplate.delete("user:" + user.getId());// 2. 更新MySQLuserMapper.updateUser(user);
}

如上图所示,其可能执行的流程顺序为:
1.客户端1,发起一个写操作,第一步删除缓存
2.客户端2,发起一个读操作,缓存中没有,则继续读数据库,读出来一个老数据,然后客户端2把老数据放入缓存中
3.客户端1更新数据库数据
这样就会出现缓存中存储的是旧数据,而数据库中存储的是新数据,这样就出现脏数据,所以我们一般都采取先操作数据库,再操作缓存。这样后续的读请求从数据库获取最新数据并重新填充缓存。这样的设计降低了数据不一致的风险,提升了系统的可靠性。同时,这也符合CAP定理中对于一致性(Consistency)和可用性(Availability)权衡的要求,在很多场景下,数据一致性被优先考虑。因此一般不建议使用这种方式。

3、延时双删策略

@Transactional
public void updateUser(User user) {// 1. 删除Redis缓存redisTemplate.delete("user:" + user.getId());// 2. 更新MySQLuserMapper.updateUser(user);// 3. 延迟一段时间后再次删除缓存CompletableFuture.runAsync(() -> {try {Thread.sleep(500); // 延迟500毫秒redisTemplate.delete("user:" + user.getId());} catch (InterruptedException e) {// 处理异常}});
}

延时双删策略主要用于解决在高并发场景下,由于网络延迟、并发控制等原因造成的数据库与缓存数据不一致的问题。

当更新数据库时,首先删除对应的缓存项,以确保后续的读请求会从数据库加载最新数据。
但是由于网络延迟或其他不确定性因素,删除缓存与数据库更新之间可能存在时间窗口,导致在这段时间内的读请求从数据库读取数据后写回缓存,新写入的缓存数据可能还未反映出数据库的最新变更。

所以为了解决这个问题,延时双删策略在第一次删除缓存后,设定一段短暂的延迟时间,如几百毫秒,然后在这段延迟时间结束后再次尝试删除缓存。这样做的目的是确保在数据库更新传播到所有节点,并且在缓存中的旧数据彻底过期失效之前,第二次删除操作可以消除缓存中可能存在的旧数据,从而提高数据一致性。

4、使用消息队列

@Transactional
public void updateUser(User user) {// 1. 更新MySQLuserMapper.updateUser(user);// 2. 发送消息到消息队列kafkaTemplate.send("user-update-topic", JSON.toJSONString(user));
}// 3. 在消费者服务中更新缓存
@KafkaListener(topics = "user-update-topic")
public void consumeUserUpdate(String message) {User user = JSON.parseObject(message, User.class);// 更新Redis缓存redisTemplate.opsForValue().set("user:" + user.getId(), user);
}

在高并发的业务场景中,消息队列是必不可少的技术之一。它不仅可以异步解耦,还能削峰填谷。对保证系统的稳定性是非常有意义的。
1.更新数据库
2.通过指定的topic发送到消息队列服务
3.然后消费者订阅该topic的消息,读取消息数据之后,再更新redis缓存。

5、使用 Canal 进行 MySQL binlog 同步

@Component
public class CanalClient {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@PostConstructpublic void init() {CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");try {connector.connect();connector.subscribe(".*\\..*");while (true) {Message message = connector.getWithoutAck(100);long batchId = message.getId();List<CanalEntry.Entry> entries = message.getEntries();if (batchId != -1 && entries.size() > 0) {for (CanalEntry.Entry entry : entries) {if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());if (rowChange.getEventType() == CanalEntry.EventType.UPDATE) {for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {// 处理更新操作,更新Redis缓存updateRedisCache(rowData);}}}}}connector.ack(batchId);}} finally {connector.disconnect();}}private void updateRedisCache(CanalEntry.RowData rowData) {// 根据rowData更新Redis缓存// 这里需要根据具体的数据结构来实现}
}

在数据库发生写操作时,将变更记录在binlog或类似的事务日志中,然后使用一个专门的异步服务或者监听器订阅binlog的变化(比如Canal),一旦检测到有数据更新,便根据binlog中的操作信息定位到受影响的缓存项,删除或更新缓存中的对应数据,确保缓存与数据库保持一致。
 

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

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

相关文章

STM32设计学生宿舍监测控制系统

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 随着科技的飞速发展和智能化时代的到来&#xff0c;学生宿舍的安全、舒适…

算法--解决二叉树遍历问题

第一 实现树的结构 class Node(): # 构造函数&#xff0c;初始化节点对象&#xff0c;包含数据和左右子节点 def __init__(self, dataNone): self.data data # 节点存储的数据 self.left None # 左子节点&#xff0c;默认为None self.rig…

【C#】C#编程入门指南:构建你的.NET开发基础

文章目录 前言&#xff1a;1. C# 开发环境 VS的基本熟悉2. 解决方案与项目的关系3. 编辑、编译、链接、运行4. 托管代码和CLR4.1 CLR&#xff1a;4.2 C# 代码第编译过程&#xff08;两次编译的&#xff09; 5. 命名空间6. 类的组成与分析7. C# 的数据类型7.1 值类型7.2 引用类型…

算法闭关修炼百题计划(八)

一半来自力扣的push题单 1.最大连续1的个数II2.长度为k的无重复字符子串3.句子的相似性4.移位字符串分组5.x的平方根6.Z字形变换 1.最大连续1的个数II 给定一个二进制数组nums&#xff0c;如果最多可以翻转一个0&#xff0c;则返回数组中连续1的最大个数 之前是Window&#x…

论文笔记(五十六)VIPose: Real-time Visual-Inertial 6D Object Pose Tracking

VIPose: Real-time Visual-Inertial 6D Object Pose Tracking 文章概括摘要I. INTRODACTIONII. 相关工作III. APPROACHA. 姿态跟踪工作流程B. VIPose网络 文章概括 引用&#xff1a; inproceedings{ge2021vipose,title{Vipose: Real-time visual-inertial 6d object pose tra…

K8S containerd拉取harbor镜像

前言 接前面的环境 K8S 1.24以后开始启用docker作为CRI&#xff0c;这里用containerd拉取 参考文档 正文 vim /etc/containerd/config.toml #修改内容如下 #sandbox_image "registry.aliyuncs.com/google_containers/pause:3.10" systemd_cgroup true [plugins.…

LM2 : A Simple Society of Language Models Solves Complex Reasoning

文章目录 题目摘要简介相关工作方法论实验结果结论局限性 题目 LM2&#xff1a;简单的语言模型社会解决复杂推理问题 论文地址&#xff1a;https://aclanthology.org/2024.emnlp-main.920/ 项目地址&#xff1a; https://github.com/LCS2-IIITD/Language_Model_Multiplex 摘要…

STM32从上电开始启动过程详解(上电->分散加载->main函数)

1.硬件复位之后&#xff0c;CPU 内的时序逻辑电路首先完成如下两个工作&#xff08; 程序代码下载到内部 flash 为例&#xff0c;flash首地址 0x0800 0000&#xff09;  将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。  将 0x08000004 位置存放的向量地址装入 P…

新版Apache tomcat服务安装 Mac+Window双环境(笔记)

简介&#xff1a;Tomcat服务器器的下载和安装&#xff1a; 安装前提 1&#xff09;电脑需要有java环境&#xff0c;jdk8以上&#xff0c;否则启动不不成功 2&#xff09;已经安装Sublime⽂文件编辑软件 3&#xff09;window电脑需要显示⽂文件拓拓展名 官网&#xff08;https:…

数据重塑:长宽数据转换【基于tidyr】

在数据分析和可视化过程中&#xff0c;数据的组织形式直接影响着我们能够进行的分析类型和可视化效果。这里简单介绍两种常见的数据格式&#xff1a;长格式&#xff08;Long Format&#xff09;和宽格式&#xff08;Wide Format&#xff09;&#xff0c;以及如何使用tidyr包进行…

ollama+springboot ai+vue+elementUI整合

1. 下载安装ollama (1) 官网下载地址&#xff1a;https://github.com/ollama/ollama 这里以window版本为主&#xff0c;下载链接为&#xff1a;https://ollama.com/download/OllamaSetup.exe。 安装完毕后&#xff0c;桌面小图标有一个小图标&#xff0c;表示已安装成功&…

基于lighthouse搭建私有网盘Cloudreve【开源应用实践】

基于lighthouse搭建私有网盘Cloudreve【超高性价比】 今天给大家分享一款私人网盘神器&#xff0c;既能存放你的文件文档&#xff0c;也能替你保存那不可告人的秘密~ 香菇今天将手把手教给大家如何在腾讯云轻量应用服务器上搭建个人专属网盘 1. 既爱又恨的网盘存储 很多小伙伴…

JavaScript高级篇 - 浏览器事件详解 DOM事件以及独立封装事件Util和Ajax封装

Dom事件 Model&#xff08;特定领域对象的抽象&#xff09;、Protocal&#xff08;针对数据格式的约定&#xff09; DOM(Document Object Model&#xff0c;文档对象模型)是针对HTML文档和XML文档的一个API。DOM描绘了一个层次化的节点树&#xff0c;允许开发人员添加、移出和…

MinIO 的 S3 over RDMA 计划: 为高速人工智能数据基础设施设定对象存储新标准

随着 AI 和机器学习的需求不断加速&#xff0c;数据中心网络正在迅速发展以跟上步伐。对于许多企业来说&#xff0c;400GbE 甚至 800GbE 正在成为标准选择&#xff0c;因为数据密集型和时间敏感型 AI 工作负载需要高速、低延迟的数据传输。用于大型语言处理、实时分析和计算机视…

git config是做什么的?

git config是做什么的&#xff1f; git config作用配置级别三种配置级别的介绍及使用&#xff0c;配置文件说明 使用说明git confi查看参数 默认/不使用这个参数 情况下 Git 使用哪个配置等级&#xff1f; 一些常见的行为查看配置信息设置配置信息删除配置信息 一些常用的配置信…

使用 npm 安装 Yarn

PS E:\WeChat Files\wxid_fipwhzebc1yh22\FileStorage\File\2024-11\spid-admin\spid-admin> yarn install yarn : 无法将“yarn”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后…

常用在汽车PKE无钥匙进入系统的高度集成SOC芯片:CSM2433

CSM2433是一款集成2.4GHz频段发射器、125KHz接收器和8位RISC&#xff08;精简指令集&#xff09;MCU的SOC芯片&#xff0c;用在汽车PKE无钥匙进入系统里。 什么是汽车PKE无钥匙进入系统&#xff1f; 无钥匙进入系统具有无钥匙进入并且启动的功能&#xff0c;英文名称是PKE&…

机器学习基础05

目录 1.随机森林 1.1随机森林的介绍 1.2算法原理 1.3API 2.线性回归 2.1回归的含义 2.2线性回归 2.3损失函数 2.4多参数回归 2.5最小二乘法MSE 2.6API 1.随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。 集成…

数据库范式、MySQL 架构、算法与树的深入解析

一、数据库范式 在数据库设计中&#xff0c;范式是一系列规则&#xff0c;用于确保数据的组织和存储具有良好的结构、完整性以及最小化的数据冗余。如果不遵循范式设计&#xff0c;数据可能会以平铺式罗列&#xff0c;仅使用冒号、分号等简单分隔。这种方式存在诸多弊端&#…

SQL面试题——奔驰SQL面试题 车辆在不同驾驶模式下的时间

SQL面试题——奔驰SQL面试题 我们的表大致如下 CREATE TABLE signal_log( vin STRING COMMENTvehicle frame id, signal_name STRING COMMENTfunction name, signal_value STRING COMMENT signal value , ts BIGINT COMMENTevent timestamp, dt STRING COMMENTformat yyyy-mm…