Redis 和 Mysql 中的数据一致性问题

Redis 和 MySQL 的数据很难直接实现 强一致性,但可以通过一些策略尽量接近或实现 最终一致性。下面从两者的特性、挑战以及解决方案来分析。

Redis 和 MySQL 的特性

  • Redis
    • 是一个基于内存的高性能键值数据库,常用于缓存、分布式锁和消息队列。
    • 数据持久化(RDB、AOF)不实时,且默认不是事务性强一致的。
    • 数据更新通常是异步传播,存在瞬时不一致。
  • MySQL
    • 是关系型数据库,支持事务(ACID),保证数据的强一致性。
    • 存储在磁盘中,写操作通常比 Redis 慢。

为什么 Redis 和 MySQL 难以实现强一致性
(1) 两者的数据更新机制不同

  • Redis 的数据更新非常快,但可能异步刷盘或存在短暂的数据丢失风险。
  • MySQL 保证数据的事务性写入,但性能相对 Redis 较慢。

(2) 分布式 CAP 理论限制

  • Redis 和 MySQL 在不同的系统中,相当于分布式场景。根据 CAP 理论:
  • 如果优先保证高可用性和分区容错性,就难以完全保证一致性。
  • Redis 通常更注重性能和高可用性。

(3) 数据的写入顺序问题

  • 如果数据先写入 Redis 再写入 MySQL,或反之,可能因网络延迟、宕机等问题导致数据不一致。

使用延时双删策略保证数据一致性

为什么需要延时双删?

  • 并发写问题:在高并发场景下,如果数据库更新完成后,另一个线程在缓存被删除后立即重建了缓存,则会导致缓存中的数据是旧的,出现不一致问题。
  • 延时删的目的:延时处理可以避免在写数据库过程中其他线程产生旧数据的缓存,确保数据最终一致。

延时双删的伪代码

public void updateData(String key, Object newValue) {// 1. 第一次删除缓存redisTemplate.delete(key);// 2. 更新数据库databaseService.updateData(key, newValue);// 3. 延时后再次删除缓存new Thread(() -> {try {Thread.sleep(500); // 延时 500msredisTemplate.delete(key); // 第二次删除} catch (InterruptedException e) {e.printStackTrace();}}).start();
}

延时双删策略主要适用于以下场景

  • 数据读写频繁:需要频繁更新数据库和缓存。
  • 对数据一致性要求较高:不能容忍缓存和数据库的数据不一致。
  • 允许轻微延迟:延时双删会增加一定的延迟,适合对响应时间要求不太苛刻的业务。

优点

  • 简单易实现:逻辑清晰,不需要引入额外复杂组件。
  • 最终一致性:延时删除第二次可以大概率解决缓存和数据库的不一致问题。

缺点

  • 延迟窗口的问题
    • 如果延时过长,仍然可能导致短时间内的数据不一致。
    • 如果延时过短,可能无法解决并发问题。
  • 对缓存穿透的影响
    • 第一次删除和数据库更新之间,可能会引发缓存穿透,增加数据库压力。
  • 代码复杂性
    • 需要额外的线程或定时任务来完成延时删除。

优化策略

  1. 结合分布式锁
    • 在写操作时使用分布式锁,确保缓存更新的唯一性,减少并发修改时缓存数据不一致的问题。
  2. 延时队列
    • 使用如 RabbitMQ 或 Redis 的延时队列,定时触发二次删除操作。
  3. 异步处理
    • 使用异步任务调度框架(如 Quartz 或 Spring Task)执行延时删除。

延时时间的控制

延时双删策略的延时时间主要取决于以下几个因素:
1. 数据库更新耗时

  • 关键点:延时时间应该覆盖数据库更新操作的耗时,避免在缓存删除后,数据库还未完成更新的情况下,有其他请求重新加载旧数据到缓存。
  • 建议:延时时间可以略大于数据库更新操作的平均耗时。
    • 如果数据库更新操作通常耗时在 100ms 左右,可以设置延时为 300~500ms。

2. 系统的并发访问特性
如果系统的读请求频率较高,并发量大,容易在第一次删除缓存后、数据库更新完成前出现缓存重建的情况,需要适当延长延时时间。

  • 高并发场景:500ms~1秒 或更长。
  • 普通场景:200ms~500ms 即可。

3. 数据一致性的业务需求

  • 对一致性要求高(如支付、库存等关键数据):建议选择更保守的延时时间,例如 500ms~1秒,以确保在极端情况下也能保持一致性。
  • 对实时性要求高,但容忍短暂不一致(如浏览量统计、推荐数据):可以选择较短的延时时间,例如 100ms~200ms。

4. 业务对延时的敏感性

  • 如果延时时间过长,会增加缓存缺失的时间窗口,从而导致更多的请求直接访问数据库,可能增加数据库的压力。因此延时应尽量平衡一致性与性能之间的关系。

推荐值
综合考虑,延时时间通常设置为 300ms~500ms,在大多数场景下是较为合理的起始值。如果业务对性能或一致性有特殊需求,可以进行以下调整:

  • 高并发系统:500ms~1秒。
  • 普通业务场景:300ms~500ms。
  • 轻量级读写系统:100ms~300ms。

如何精准确定延时
为了选择最佳延时,可以采用以下方法:

  1. 分析实际耗时
    • 测量数据库写操作的平均耗时和 p99(99%分位数)耗时,取稍大的值作为参考。
  2. 逐步调优
    • 从 300ms 开始设置,观察系统在高并发情况下的一致性和性能表现,根据实际情况适当调整。
  3. 模拟压测
    • 在模拟高并发的环境中测试不同延时时间的效果,找到平衡点。

注意事项

  • 动态调整:延时值可以结合系统的监控数据动态调整。例如,实时统计数据库更新的平均耗时,动态优化延时时间。
  • 避免过长延时:延时过长会导致缓存命中率下降,增加数据库压力,需适度权衡。

通过合理选择延时时间,可以最大限度地降低延时双删策略的弊端,实现高效的一致性保障。

使用消息队列保证一致性

使用 消息队列 来保证 RedisMySQL 数据一致性 是一种常见的解决方案,可以有效应对高并发场景中的数据同步问题。以下是该方法的具体流程、实现步骤及优缺点分析。
实现流程
假设业务场景需要更新 MySQL 数据,并保持 Redis 缓存一致性:

步骤 1:更新 MySQL 数据

  • 执行数据库更新操作,将数据更新到 MySQL。
  • 数据库操作成功后,向消息队列(如 RabbitMQ、Kafka、RocketMQ)发送一条消息,通知其他服务或缓存更新系统。

步骤 2:消息队列的缓存同步任务

  • 消息队列的消费者订阅该消息。
  • 消费者接收到消息后,删除 Redis 缓存或更新 Redis 数据。
  • 如果更新 Redis 失败,可以重新处理该消息或记录日志,等待人工介入。

伪代码实现
生产者:更新 MySQL 和发送消息
生产者负责更新 MySQL 数据并发送消息到队列。

@Transactional
public void updateData(String key, Object newValue) {// 1. 更新 MySQL 数据databaseService.update(key, newValue);// 2. 发送消息到消息队列String message = "UPDATE_CACHE:" + key; // 构造更新缓存的消息messageQueue.send(message); // 假设 messageQueue 是 MQ 的工具类
}

消费者:更新或删除 Redis 缓存
消费者负责监听消息队列并处理缓存同步。

@RabbitListener(queues = "cacheUpdateQueue") // 假设使用 RabbitMQ
public void handleCacheUpdate(String message) {// 1. 解析消息if (message.startsWith("UPDATE_CACHE:")) {String key = message.split(":")[1];// 2. 删除 Redis 缓存(或更新缓存)redisTemplate.delete(key);// 3. 如果需要,也可以重新加载 MySQL 数据到 RedisObject newValue = databaseService.findByKey(key);redisTemplate.opsForValue().set(key, newValue);}
}

消息队列对一致性的保障
强一致性
通过消息队列的事务机制(如 RocketMQ 的事务消息、Kafka 的幂等消费机制),可以实现 Redis 和 MySQL 数据的强一致性。

  • 在 MySQL 更新成功后,必须确保消息成功发送到队列。
  • 消息消费失败时可以进行重试,确保 Redis 数据最终更新成功。

最终一致性
通常,消息队列的方式更适合 最终一致性 的场景:

  • 如果 Redis 的缓存更新失败,消息队列可以通过重试机制或死信队列(DLQ)进行补偿。
  • Redis 数据的更新是异步的,因此可能有短暂的不一致。

为什么消息队列可以保证一致性?

  1. 解耦生产者和消费者
    • 数据库更新和缓存更新通过消息队列解耦,降低了直接调用的复杂度。
  2. 可靠传输
    • 消息队列可以保证消息的持久化,即使 Redis 出现故障,消息不会丢失。
  3. 异步处理
    • Redis 的更新可以异步进行,不会阻塞主业务逻辑,提高了系统性能。
  4. 重试机制
    • 消费者在处理消息失败时,可以重试或记录异常,保证消息最终被正确消费。

消息队列的事务处理
为了确保消息队列和 MySQL 操作的一致性,可以使用 事务消息机制
解决 MySQL 和消息队列一致性的方法

  1. 本地事务 + 消息队列事务
    • 先执行 MySQL 的事务操作,随后发送事务性消息到消息队列(如 RocketMQ)。
    • 当消息队列确认消息投递后,才提交 MySQL 事务。
  2. 事件表方案
    • 在 MySQL 中设计一个 event_log 表,记录要发送到消息队列的事件。
    • 定期扫描该表,将消息发送到消息队列。
    • 发送成功后,将事件从 event_log 表中删除。

优缺点分析
优点

  1. 高性能:Redis 更新是异步的,不会阻塞数据库写操作。
  2. 解耦性强:数据库和缓存的操作通过消息队列隔离,降低了模块间的耦合。
  3. 最终一致性:通过消息队列的可靠性和重试机制,确保数据最终一致。

缺点

  1. 复杂性增加:引入消息队列后,系统架构复杂度上升。
  2. 短暂不一致:在消息队列未消费完成时,Redis 和 MySQL 数据可能短时间不一致。
  3. 重试成本高:如果消息消费失败,处理逻辑可能较为复杂。

适用场景

  • 高并发场景:例如秒杀、抢购、推荐系统等需要高性能缓存的场景。
  • 弱实时性场景:如电商订单、用户行为分析等,允许短暂的不一致。
  • 最终一致性需求:例如库存同步、积分更新等场景。

事务性缓存:结合分布式事务

  • Seata 的配置需要两个关键部分:Seata Server 和 Seata Client。
  • Seata Server 是一个独立的微服务,它负责协调分布式事务的管理。作为一个事务协调器,Seata Server 处理多个服务之间的事务逻辑,包括事务的开始、提交和回滚。它通常需要单独部署,并与使用它的微服务应用进行通信,以实现事务的协调和一致性。在生产环境中可以将其部署多个实例,以此来保证高可用性和稳定性。
  • Seata Client 不是一个独立的微服务,而是嵌入到需要使用分布式事务的应用中。每个微服务应用在其代码中集成 Seata Client,负责向 Seata Server 注册和发送事务相关的请求。Seata Client 通过拦截应用的数据库操作(如 SQL 语句)和其他资源的操作,实现分布式事务的管理。因此,它与应用程序的生命周期紧密相关,并与 Seata Server 进行交互以完成事务的协调。
  • Seata Server 中有两个配置文件:file.conf 和 registry.conf ,确保服务能够正常注册和运行。
  • registry.conf 文件用于配置 Seata Server 的注册中心。
registry {type = "nacos"  # 使用 Nacos 作为注册中心nacos {serverAddr = "127.0.0.1:8848"  # Nacos 服务器地址namespace = "public"  # Nacos 命名空间cluster = "default"  # Nacos 集群名serviceName = "seata-server"  # Seata Server 注册的服务名}
}
  • file.conf 文件用于配置 Seata Server 的存储方式。下面是一个使用 MySQL 数据库和 Redis 的示例配置:
store {mode = "db"  # 数据存储模式,这里选择数据库模式db {datasource {driverClassName = "com.mysql.cj.jdbc.Driver"  # MySQL 驱动url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf8"  # 数据库连接 URLuser = "root"  # 数据库用户名password = "password"  # 数据库密码}}# Redis 配置部分(示例)redis {mode = "single"  # Redis 单实例模式single {address = "127.0.0.1:6379"  # Redis 服务器地址password = ""  # Redis 密码(如有)database = 0  # 使用的数据库索引}}
}

Seata Client 的配置是在 application.yml 文件中的。

spring:application:name: spring-boot-seata-client  # 应用程序名称cloud:alibaba:seata:tx-service-group: my_tx_group  # 分布式事务服务组名称seata:enabled: true  # 启用 Seataapplication-id: spring-boot-seata-client  # Seata Client 的应用 IDtx-service-group: my_tx_group  # 事务服务组,需与 Seata Server 配置一致enable-auto-data-source-proxy: true  # 启用自动数据源代理client:rm:report-success-enable: true  # 是否启用事务成功报告transport:type: "TCP"  # 网络传输协议,通常为 TCPgroup: "default"  # 服务组名thread-count: 8  # 线程数量# 其他传输配置

创建一个服务类 OrderService 来演示分布式事务:

@Service
public class OrderService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;/*** 使用 Seata 的 @GlobalTransactional 注解管理分布式事务*/@GlobalTransactionalpublic void createOrderAndUpdateStock(String orderId, String productId, int quantity) {// Step 1: 更新 MySQL 中的订单String insertOrderQuery = "INSERT INTO orders (order_id, product_id, quantity) VALUES (?, ?, ?)";jdbcTemplate.update(insertOrderQuery, orderId, productId, quantity);// Step 2: 更新 Redis 中的库存String redisStockKey = "product_stock_" + productId;Integer stock = redisTemplate.opsForValue().get(redisStockKey);if (stock == null || stock < quantity) {throw new RuntimeException("库存不足");}redisTemplate.opsForValue().set(redisStockKey, stock - quantity);}
}

推荐的实践方案

高并发读多写少场景(如电商商品信息缓存)

  • 推荐采用延迟清除策略或消息队列同步,保证最终一致性。

强一致性需求的场景(如账户余额管理)

  • 使用事务性缓存或先写 MySQL,再更新 Redis,严格控制写入顺序。

性能优先的场景(如秒杀库存管理)

  • 使用 Redis 作为主存储,异步将数据持久化到 MySQL。

Redis 和 MySQL 天然不支持强一致性,尤其是高并发场景下的数据一致性管理需要权衡性能和一致性:

  • 实现最终一致性是常见选择。
  • 如果强一致性是硬性要求,可以通过分布式事务框架或严格的操作顺序来实现,但会牺牲一定的性能。
  • 具体方案应根据业务需求(读写频率、性能要求、一致性需求等)选择适合的策略。

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

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

相关文章

数据结构 ——二叉树转广义表

数据结构 ——二叉树转广义表 1、树转广义表 如下一棵树&#xff0c;转换为广义表 root(c(a()(b()()))(e(d()())(f()(j(h()())())))) (根&#xff08;左子树&#xff09;&#xff08;右子树&#xff09;) 代码实现 #include<stdio.h> #include<stdlib.h>//保存…

企业车辆管理系统(源码+数据库+报告)

一、项目介绍 352.基于SpringBoot的企业车辆管理系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块 二、项目技术 编程语言&#xff1a;Java 数据库&#xff1a;MySQL 项目管理工具&#xff1a;Maven 前端技术&#xff1a;Vue 后端技术&a…

ubuntu20.04复现 Leg-KILO

这里写目录标题 opencv版本问题下载3.2.0源代码进入解压后的目录创建构建目录运行 CMake 配置 配置时指定一个独立的安装目录&#xff0c;例如 /opt/opencv-3.2&#xff1a;出错&#xff1a; 使用多线程编译错误1&#xff1a; stdlib.h: 没有那个文件或目录错误2&#xff1a;er…

如何保证开源AI呼入机器人和AI呼出机器人的数据安全性?

如何保证开源AI呼入机器人和AI呼出机器人的数据安全性&#xff1f; 作者&#xff1a;开源呼叫中心FreeIPCC 确保开源AI呼入机器人和AI呼出机器人的数据安全性是部署这些智能系统时不可或缺的一部分。随着越来越多的企业依赖于自动化客户服务和外呼营销&#xff0c;保护用户隐…

基于SpringBoot+vue的高校学生成绩管理系统

目录 一、绪论1.1 开发背景1.2 系统开发平台1.2.1 Vue简介1.2.2 IDEA简介1.2.3 MySQL简介 1.3 系统开发环境 二、需求分析2.1 系统需求分析2.2 系统数据流图 三、概要设计3.1 业务流程分析 四、详细设计4.1 系统功能结构图4.2 E-R模型4.3 数据库表设计 五、模块实现5.1 管理员主…

微信小程序苹果手机自带的数字键盘老是弹出收起,影响用户体验,100%解决

文章目录 1、index.wxml2、index.js3、index.wxss1、index.wxml <!--index.wxml--> <view class="container"><view class="code-input-container"><view class="code-input-boxes"><!-- <block wx:for="{{…

Docker--Docker Image(镜像)

什么是Docker Image&#xff1f; Docker镜像&#xff08;Docker Image&#xff09;是Docker容器技术的核心组件之一&#xff0c;它包含了运行应用程序所需的所有依赖、库、代码、运行时环境以及配置文件等。 简单来说&#xff0c;Docker镜像是一个轻量级、可执行的软件包&…

RabbitMQ的核心组件有哪些?

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ的核心组件有哪些&#xff1f;】面试题。希望对大家有帮助&#xff1b; RabbitMQ的核心组件有哪些&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RabbitMQ是一个开源的消息代理&#xff08;Messag…

[OpenGL] Transform feedback 介绍以及使用示例

一、简介 本文介绍了 OpenGL 中 Transform Feedback 方法的基本概念和代码示例。 二、Transform Feedback 介绍 1. Transform Feedback 简介 根据 OpenGL-wiki&#xff0c;Transform Feedback 是捕获由顶点处理步骤&#xff08;vertex shader 和 geometry shader&#xff0…

【Linux】—简单实现一个shell(myshell)

大家好呀&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流哦&#xff01; 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&…

什么是Apache日志?为什么Apache日志分析很重要?

Apache是全球最受欢迎的Web服务器软件&#xff0c;支持约30.2%的所有活跃网站。凭借其可靠性、灵活性和强大的功能&#xff0c;Apache数十年来一直是互联网的中坚力量。 一、Apache Web服务器的工作原理 Apache Web服务器的工作原理如下&#xff1a; 接收HTTP请求&#xff1…

如何使用 uni-app 构建直播应用程序?

使用uni-app构建直播应用程序涉及前端和后端的开发&#xff0c;以及音视频处理技术的选择。下面我将概述一个典型的直播应用架构&#xff0c;并详细说明如何在uni-app中实现关键功能。 直播应用架构 前端&#xff08;uni-app&#xff09;&#xff1a;负责用户界面展示、互动逻…

Spring Security 6 系列之一 - 开篇入门

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级为6.3.0&#xff0c;关键是其风…

好用的工单系统,适用于各种场景

项目名称 smart 工单系统 项目简介 Smart-API 工单系统是基于 Go 语言开发的后台管理系统&#xff0c;前后端分离&#xff0c;采用 Gin 框架作为后端&#xff0c;Vue.js 和 Element UI 作为前端。系统旨在实现对工单管理的高效处理&#xff0c;支持任务分配、执行监控、…

docker 搭建自动唤醒UpSnap工具

1、拉取阿里UpSnap镜像 docker pull crpi-k5k93ldwfc7o75ip.cn-hangzhou.personal.cr.aliyuncs.com/upsnap/upsnap:4 2、创建docker-compose.yml文件&#xff0c;进行配置&#xff1a; version: "3" services:upsnap:container_name: upsnapimage: crpi-k5k93ldwf…

7.Linux - 安装MySQL、Tomcat、Nginx、RabbitMQ、Redis

Linux - 安装MySQL 文章目录 Linux - 安装MySQL一、MySQL 5.71.1 安装1.2 配置 二、MySQL 8.x2.1 安装2.2 配置 三、Tomcat安装3.1 安装 JDK3.2 Tomcat&#xff08;整的不行&#xff09; 四、Nginx4.1 安装 五、RabbitMQ5.1 安装 六、Redis6.1 安装 一、MySQL 5.7 1.1 安装 我…

python | linux | ModuleNotFoundError: No module named ‘WFlib‘ |找不到模块

问题&#xff1a; (base) beautyby521-7:~/Website-Fingerprinting-Library-master$ bash scripts/NetCLR.sh Traceback (most recent call last):File "/home/beauty/Website-Fingerprinting-Library-master/exp/pretrain.py", line 8, in <module>from WFli…

Maven 打包(system jar 和微服务父子项目)

jar包使用system 请务必减少使用system的频率&#xff0c;除非这个jar包中央仓库中没有 在文末的build标签下写入 < includeSystemScope >true</ includeSystemScope>即可 <?xml version"1.0" encoding"UTF-8"?> <project xmlns&…

AI Agent:重塑业务流程自动化的未来力量(2/30)

《AI Agent&#xff1a;重塑业务流程自动化的未来力量》 摘要&#xff1a;整体思路是先介绍 AI Agent 的基本情况&#xff0c;再深入阐述其实现业务流程自动化的方法和在不同领域的应用&#xff0c;接着分析其价值和面临的挑战&#xff0c;最后得出结论&#xff0c;为读者全面…

深入解析:选择最适合你的Whisper语音识别模型

在语音识别领域&#xff0c;Whisper系列模型因其卓越的性能和多语言支持而备受青睐。今天&#xff0c;我们将详细解析三种不同的Whisper模型Whisper-large-v3、Belle-whisper-large-v3-zh以及Whisper-large-v3-turbo&#xff0c;帮助你根据具体需求选择最合适的版本。 一、Whi…