如何实现Redis和Mysql中数据双写一致性

一、引言

今天我们来聊聊一个在分布式系统中非常常见但又十分棘手的问题——Redis与MySQL之间的双写一致性。我们在项目中多多少少都遇到过类似的困扰,缓存是用Redis,数据库是用MySQL,但如何确保两者之间的数据一致性呢?接下来我会尽量简洁地为大家解析这个问题,并提供几个实战方案。

二、双写一致性挑战

我们先来看看什么是双写一致性。

简单来说,就是当数据同时存在于缓存(Redis)和数据库(MySQL)时,如何确保这两者之间的数据是一致的。

典型场景

  1. 写数据库后忘记更新缓存:这种情况最常见,当我们更新数据库后,缓存没有同步更新,导致读取到旧的数据。
  2. 删除缓存后数据库更新失败:在某些操作中,我们可能会先删除缓存,再更新数据库,但如果数据库更新失败,就会导致缓存和数据库的数据不一致。

在了解完双写一致性带来的挑战之后我们接下来看看几种经典的缓存模式。

三、缓存模式

3.1 Cache Aside Pattern (旁路模式)

Cache Aside Pattern是最常见的一种缓存使用模式,它的核心思想是以数据库为主,缓存为辅。

工作流程

读取操作:先从缓存中读取数据,如果缓存命中则返回结果;如果缓存未命中,则从数据库中读取数据,并将数据写入缓存。

更新操作:先更新数据库,再删除缓存中的旧数据。

示例代码:

public class CacheAsidePattern {private RedisService redis;private DatabaseService database;// 读取操作public String getData(String key) {// 从缓存中获取数据String value = redis.get(key);if (value == null) {// 缓存未命中,从数据库获取数据value = database.get(key);if (value != null) {// 将数据写入缓存redis.set(key, value);}}return value;}// 更新操作public void updateData(String key, String value) {// 更新数据库database.update(key, value);// 删除缓存中的旧数据redis.delete(key);}
}

优缺点分析:

优点:

  • 简单易懂,易于实现。
  • 读性能高,因为大部分读操作都会命中缓存。

缺点:

  • 存在短暂的不一致情况,更新数据库后缓存可能还没删除。
  • 删除缓存后,如果数据库更新失败,会导致数据不一致。

3.2 读写穿透模式

3.2.1 写穿透

当缓存未命中时,自动从数据库加载数据,并写入缓存。

3.2.2 读穿透

当缓存更新时,同步将数据写入数据库。

3.2.3 代码示例
public class ReadWriteThroughPattern {private RedisService redis;private DatabaseService database;// Read-Throughpublic String readThrough(String key) {// 从缓存中获取数据String value = redis.get(key);if (value == null) {// 缓存未命中,从数据库获取数据value = database.get(key);if (value != null) {// 将数据写入缓存redis.set(key, value);}}return value;}// Write-Throughpublic void writeThrough(String key, String value) {// 将数据写入缓存redis.set(key, value);// 同步将数据写入数据库database.update(key, value);}
}
3.2.4 优缺点分析

优点:

  • 保证了数据的强一致性,缓存和数据库的数据始终同步。
  • 读写操作都由缓存处理,数据库压力较小。

缺点:

  • 写操作的延迟较高,因为每次写入缓存时都需要同步写入数据库。
  • 实现复杂度较高,需要额外的缓存同步机制。

3.3 异步缓存写入(Write Behind)

缓存更新后,异步批量写入数据库。这种策略适用于可以容忍一定数据不一致的高性能场景。

3.3.1 示例代码
public class WriteBehindPattern {private RedisService redis;private DatabaseService database;private UpdateQueue updateQueue;// 异步缓存写入public void writeBehind(String key, String value) {// 将数据写入缓存redis.set(key, value);// 异步将数据写入数据库asyncDatabaseUpdate(key, value);}private void asyncDatabaseUpdate(String key, String value) {// 异步操作,将更新请求放入队列updateQueue.add(new UpdateTask(key, value));}
}
3.3.2 优缺点分析

优点:

  • 写操作的性能非常高,因为只需更新缓存,数据库更新是异步进行的。
  • 适用于对写操作性能要求较高的场景。

缺点:

  • 存在数据不一致的风险,缓存更新后数据库可能还未更新。
  • 实现复杂度较高,需要处理异步操作中的异常和重试。

四、实战解析

4.1 延时双删策略

延时双删策略的核心思想是:在更新数据库后,先删除一次缓存,然后延迟一段时间再删除一次缓存,减少数据不一致的风险。

关键是如何确定延迟时间,这个时间需要根据系统的具体情况来调整,以平衡一致性和性能。

public class DelayedDoubleDeletePattern {private RedisService redis;private DatabaseService database;private ScheduledExecutorService scheduledExecutorService;private long delay = 500; // 延迟时间,单位:毫秒// 更新操作public void updateDataWithDelay(String key, String value) {// 更新数据库database.update(key, value);// 删除缓存中的旧数据redis.delete(key);// 延迟一段时间再删除缓存scheduledExecutorService.schedule(() -> redis.delete(key), delay, TimeUnit.MILLISECONDS);}
}

优缺点分析:

优点:

  • 简化了缓存和数据库的一致性问题。
  • 避免了缓存和数据库的同步更新,提高了系统性能。

缺点:

  • 需要精确控制延迟时间,否则可能导致缓存和数据库不一致。
  • 实现相对复杂,需要额外的定时任务管理。

4.2 删除缓存重试机制

删除缓存时,如果失败,可以设置重试机制,以确保缓存最终被删除。

通过使用Spring的@Retryable注解,可以简化重试逻辑。

代码示例:

public class CacheService {private RedisService redis;@Retryable(value = Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 2000))public void deleteCache(String key) {// 删除缓存中的数据redis.delete(key);}
}

优缺点分析:

优点:

  • 确保缓存最终被删除,降低数据不一致的风险。
  • 使用Spring的重试机制,简化实现逻辑。

缺点:

  • 需要处理重试的多次失败情况,可能导致系统负载增加。
  • 适用于缓存删除失败率较低的场景。

4.3 监听binlog异步删除缓存

利用数据库的binlog变更来异步更新缓存,通过消息队列和异步服务解耦缓存更新操作。

通过订阅binlog,将变更记录放入消息队列,然后由异步服务处理缓存更新。

代码示例:

public class BinlogListenerPattern {private RedisService redis;private MessageQueue messageQueue;// 订阅binlogpublic void onBinlogChange(BinlogEntry entry) {// 将变更记录放入消息队列messageQueue.send(new CacheUpdateMessage(entry.getKey()));}// 异步服务处理缓存更新public void processCacheUpdate(CacheUpdateMessage message) {// 删除缓存中的数据redis.delete(message.getKey());}
}

优缺点分析:

优点:

  • 利用数据库的变更日志,保证缓存和数据库的一致性。
  • 异步处理提高了系统性能,降低了实时更新的压力。

缺点:

  • 实现复杂度较高,需要处理消息队列和异步服务。
  • 存在延迟更新的情况,可能导致短时间内的数据不一致。

五、小结

在实际项目中,我们需要根据具体的业务场景来选择最合适的一致性策略。同时,在高并发场景下,可以结合分布式锁和消息队列来确保数据一致性。异步处理中的异常处理和重试策略也非常重要,能够有效提高系统的稳定性和可靠性。

此外,在实际开发应用时,不需要自己再去实现一套缓存管理代码,有很多框架已经提供了基于声明式注解的缓存管理器抽象,只需要添加几个注解,就可以实现数据库缓存,例如:

  • Spring Cache
  • Alibaba Jetcache

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

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

相关文章

面试篇 - Transformer前馈神经网络(FFN)使用什么激活函数?

1. FFN结构分解 原始Transformer的FFN层 FFN(x) max(0, xW₁ b₁)W₂ b₂ # 原始论文公式 输入:自注意力层的输出 x(维度 d_model512) 扩展层:xW₁ b₁(扩展为 d_ff2048) 激活函数:Re…

基于Python Flask的深度学习电影评论情感分析可视化系统(2.0升级版,附源码)

博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&#x1f3…

前端vue2修改echarts字体为思源黑体-避免侵权-可以更换为任意字体统一管理

1.下载字体 npm install fontsource/noto-sans-sc 不知道为什么我从github上面下载的不好使,所以就用了npm的 2.引用字体 import fontsource/noto-sans-sc; 在入口文件-main.js中引用 3.设置echats模板样式 import * as echarts from echarts; // 在import的后…

51c自动驾驶~合集37

我自己的原文哦~ https://blog.51cto.com/whaosoft/13878933 #DETR->DETR3D->Sparse4D 走向长时序稀疏3D目标检测 一、DETR 图1 DETR架构 DETR是第一篇将Transformer应用到目标检测方向的算法。DETR是一个经典的Encoder-Decoder结构的算法,它的骨干网…

【MongoDB篇】MongoDB的集合操作!

目录 引言第一节:集合的“诞生”——自动出现还是手动打造?🤔第二节:集合的“查阅”——看看这个数据库里有哪些柜子?📂👀第三节:集合的“重命名”——给文件柜换个名字!…

Goland终端PowerShell命令失效

Goland终端Terminal的PowerShell不能使用,明明windows上升级了PowerShell 7设置了配置文件,但是只能在windows终端下使用,goland终端下直接失效报错,安装升级PowerShell请看Windows11终端升级PowerShell7 - HashFlag - 博客园 问…

简单分析自动驾驶发展现状与挑战

一、技术进展与市场渗透 技术分级与渗透率 当前量产乘用车的自动驾驶等级以L2为主(渗透率约51%),L3级处于初步落地阶段(渗透率约20%),而L4级仍处于测试和示范运营阶段(渗透率约11%)2…

【C++类和数据抽象】消息处理示例(1):从设计模式到实战应用

目录 一、数据抽象概述 二、消息处理的核心概念 2.1 什么是消息处理? 2.2 消息处理的核心目标 三、基于设计模式的消息处理实现 3.1 观察者模式(Observer Pattern) 3.2 命令模式(Command Pattern) 四、实战场景…

【统计方法】交叉验证:Resampling, nested 交叉验证等策略 【含R语言】

Resampling (重采样方法) 重采样方法是从训练数据中反复抽取样本,并在每个(重新)样本上重新调整模型,以获得关于拟合模型的附加信息的技术。 两种主要的重采样方法 Cross-Validation (CV) 交叉验证 : 用于估计测试误…

常见的 CSS 知识点整理

1. 盒模型(Box Model)是什么?标准盒模型和 IE 盒模型的区别? 答案: CSS 盒模型将元素视为一个盒子,由内容(content)、内边距(padding)、边框(bor…

Educational Codeforces Round 178 div2(题解ABCDE)

A. Three Decks #1.由于最后三个数会相等&#xff0c;提前算出来和&#xff0c;%3判断&#xff0c;再判前两个数是否大于 #include<iostream> #include<vector> #include<stdio.h> #include<map> #include<string> #include<algorithm> #…

如何创建一个导入模板?全流程图文解析

先去找到系统内可以上传东西的按钮 把你的模板上传上去,找到对应的fileName 图里的文字写错了,是复制粘贴"filePath"到URL才能下载

通信原理第七版与第六版区别附pdf

介绍 我用夸克网盘分享了「通信原理 第7版》樊昌信」&#xff0c;链接&#xff1a;https://pan.quark.cn/s/be7c5af4cdce 《通信原理&#xff08;第7版&#xff09;》是在第6版的基础上&#xff0c;为了适应当前通信技术发展和教学需求&#xff0c;并吸取了数十所院校教师的反…

Mysql唯一性约束

唯一性约束&#xff08;Unique Constraint&#xff09;是数据库设计中用于保证表中某一列或多列组合的值具有唯一性的一种规则。它可以防止在指定列中插入重复的数据&#xff0c;有助于维护数据的完整性和准确性。下面从几个方面为你详细解释 作用 确保数据准确性&#xff1a…

测试基础笔记第十六天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、UI自动化介绍1.认识UI自动化测试2.实施UI自动化测试前置条件3.UI自动化测试执行时机4.UI自动化测试核心作用和劣势 二、认识Web自动化测试工具-Selenium021.Sel…

PaddleX的安装

参考&#xff1a;安装PaddlePaddle - PaddleX 文档 1、安装PaddlePaddle 查看 docker 版本 docker --version 若您通过 Docker 安装&#xff0c;请参考下述命令&#xff0c;使用飞桨框架官方 Docker 镜像&#xff0c;创建一个名为 paddlex 的容器&#xff0c;并将当前工作目…

长效住宅IP是什么?如何获取长效住宅IP?

在当今的互联网世界里&#xff0c;IP地址作为连接用户与网站之间的桥梁&#xff0c;其重要性不言而喻。对于跨境电商、社交媒体运营以及数据采集等领域的专业人士而言&#xff0c;普通的IP地址已无法满足日益复杂的需求。他们更需要一种稳定、安全且持久的长效住宅IP来完成各类…

02 业务流程架构

业务流程架构提供了自上而下的组织鸟瞰图&#xff0c;是业务流程的全景图。根据所采用的方法不同&#xff0c;有时被称为流程全景图或高层级流程图&#xff0c;提供了业务运营中所有业务流程的整体视图。 这样有助于理解企业内部各个业务流程之间的相互关系以及它们如何共同工…

jenkins slave节点打包报错Failed to create a temp file on

jenkins slave节点打包报错 一、报错信息 FATAL: Unable to produce a script file Also: hudson.remoting.Channel$CallSiteStackTrace: Remote call to slave-83at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1784)at hudson.remoting.UserRequest$…

什么是 Swagger 以及如何在 Spring Boot 中实现 Swagger:配置与实践指南

在现代 RESTful API 开发中&#xff0c;Swagger 是一种广泛使用的工具&#xff0c;用于生成、描述和可视化 API 文档。它极大地简化了 API 的开发、测试和维护过程。结合 Spring Boot&#xff0c;Swagger 可以快速集成到项目中&#xff0c;生成交互式 API 文档&#xff0c;方便…