Redis与MySQL数据一致性问题的策略模式及解决方案

news2024/12/26 14:29:24

目录

一、策略模式

1、旁路缓存模式(Cache Aside Pattern)

2、读写穿透(Read-Through/Write-Through)

3、异步缓存写入(Write Behind)

二、一致性解决方案

1、缓存延迟双删

2、删除重试机制

3、读取biglog异步删除缓存

三、总结


在开发中,一般会使用Redis缓存一些常用的热点数据用来减少数据库IO,提高系统的吞吐量

先了解一下分布式系统中的一致性概念。

  • 强一致性:所有节点的数据必须实时同步,保证任何时候读取到的数据都是最新的。

  • 弱一致性:系统允许数据暂时不一致,但最终会达到一致状态。

  • 最终一致性:数据更新后,经过一段时间,系统会逐步达到一致状态。这个时间不固定,但在业务允许的范围内。

双写一致性:当数据同时存在于缓存(Redis)和数据库(MySQL)时,两者之间数据一致

 那么容易出现数据一致性问题的场景是:

  • 数据写入数据库,未更新缓存
  • 删除缓存后,数据库更新失败

一、策略模式

缓存可以提升性能、缓解数据库压力,但是使用缓存也会导致数据不一致性的问题。有三种经典的缓存使用模式:

  • Cache-Aside Pattern

  • Read-Through/Write-through

  • Write-behind

1、旁路缓存模式(Cache Aside Pattern)

Cache Aside Pattern的提出是为了尽可能地解决缓存与数据库的数据不一致问题

流程:

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

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

在日常开发中,一般使用了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);
    }
}

 ❓:Cache-Aside在操作数据库时,为什么是先操作数据库呢?为什么不先操作缓存呢?

1、先删除缓存后,数据库更新失败

🔺线程1:删除缓存A,由于网络问题没有操作数据库失败

🔻线程2:查询A,缓存无数据,并把A写入缓存

🔺线程1:网络堵塞结束,修改数据库A为B

那么此时缓存是A【旧数据】,数据库是B【新数据】,脏数据出现啦!!!

因此,Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存

2、先操作数据库再删除缓存方案

🔺线程1:操作数据库,A更新数据为B,删除缓存A

🔻线程2:查询A,缓存无数据,并把B写入缓存

这种方案下,在数据库更新成功后到删除Redis缓存数据之前的这段时间中,其他线程读取的数据都是旧数据,等Redis删除缓存后会重新从数据库中读取最新数据同步到Redis,这样可以在一定程度上保证数据的最终一致性

但是在极端情况下,线程1的缓存删除失败,线程2读取的也就是旧数据A,而不是新数据B了

这种方案也就是旁路缓存模式,那么Cache-Aside的优缺点就是:

优点

  • 简单易懂,易于实现

  • 读性能高,因为大部分读操作都会命中缓存

缺点

  • 更新数据库后缓存可能还没删除,存在短暂的不一致

  • 删除缓存后,如果数据库更新失败,会导致数据不一致

 ❓:Cache-Aside在写入请求的时候,为什么是删除缓存而不是更新缓存呢?

🔺线程1:操作数据库,更新数据为A,由于网络问题未更新缓存

🔻线程2:操作数据库,更新数据为B,更新缓存为B

🔺线程1:网络堵塞结束,更新缓存为A

那么此时缓存是A【旧数据】,数据库是B【新数据】,脏数据出现啦!!!

如果是删除缓存取代更新缓存则不会出现这个脏数据问题!!!

因此,Cache-Aside缓存模式,选择了删除缓存而不是更新缓存

适应场景:适用于读多写少的场景,特别是对数据一致性要求不是特别高的应用

2、读写穿透(Read-Through/Write-Through)

Read-Through:当缓存未命中时,自动从数据库加载数据,并写入缓存

Write-Through:当缓存更新时,同步将数据写入数据库

和旁路缓存模式很像,只有写操作不太一样

public class ReadWriteThroughPattern {

    private RedisService redis;
    private DatabaseService database;

    // Read-Through
    public 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-Through
    public void writeThrough(String key, String value) {
        // 将数据写入缓存
        redis.set(key, value);
        // 同步将数据写入数据库
        database.update(key, value);
    }
}

优点

  • 保证了数据的强一致性,缓存和数据库的数据始终同步。

  • 读写操作都由缓存处理,数据库压力较小。

缺点

  • 写操作的延迟较高,因为每次写入缓存时都需要同步写入数据库,增加了系统的响应时间

  • 实现复杂度较高,需要额外的缓存同步机制

适应场景:适合读多写多、且对数据一致性要求较高的场景

3、异步缓存写入(Write Behind)

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

示例代码:

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));
    }
}

优点

  • 写操作的性能非常高,因为只需更新缓存,数据库更新是异步进行的

  • 适用于对写操作性能要求较高的场景

缺点

  • 存在数据不一致的风险,缓存更新后数据库可能还未更新。

  • 实现复杂度较高,需要处理异步操作中的异常和重试

适应场景:大批量数据读取,允许短期数据不一致,写密集型场景

二、一致性解决方案

缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP

CAP理论,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

没办法做到数据库与缓存绝对的一致性,但通过一些方案优化处理,是可以保证弱一致性,最终一致性

1、缓存延迟双删

流程:

  1. 先删除缓存

  2. 再更新数据库

  3. 休眠一会(比如1秒),再次删除缓存

     但休眠的时间内,可能有脏数据,且第二次删除也可能失败,导致的数据不一致问题

     延迟双删策略只能保证最终的一致性,不能保证强一致性。由于对Redis的操作和Mysql的操作不是原子性操作,所以如果想保证数据的强一致性就需要加锁控制,如下图所示

加锁之后势必会带来系统的吞吐量的下降,所以需要衡量利弊来确定是否使用加锁

方案优化:删除失败就多删除几次呀,保证删除缓存成功就可以了!

所以可以引入删除缓存重试机制

2、删除重试机制

删除缓存失败,则将这些key放入到消息队列中,消费消息队列的消息,获取要删除的key,重试删除缓存操作

3、读取biglog异步删除缓存

重试删除缓存机制还可以吧,就是会造成好多业务代码入侵

方案优化:通过数据库的binlog来异步淘汰key

        以MySQL为例,通过canal监听binlog日志感知数据的变动后,canal客户端执行删除Redis缓存数据,如果缓存数据删除失败那么发送一条MQ消息让canal客户端继续执行删除操作,这样可以保证数据的最终一致性,但是这样也增加了系统的复杂性

三、总结

(1)实际开发中一般使用使用了Cache Aside Pattern缓存更新策略模式,此方案最大程度上保证了数据的一致性并且实现也最简单

(2)无论是先操作数据库再删除缓存还是先删除缓存再操作数据库都有可能会出现删除缓存失败的情况,所以需要加入删除重试机制

(3)如果想要Redis和Mysql的数据强一致性,可以考虑使用加锁的方式实现

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

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

相关文章

30.【C语言】函数系列下

1.嵌套调用 *定义:函数之间的互相调用 *例: int function1(int a, int b) {function2(b);//嵌套函数的调用return a; } //注意:不能将function2定义在function1的里面,这不叫嵌套函数的调用 void function2(int c) {} #include…

聊一聊 Node.js(Express)的 req.body、req.params 和 req.query 区别和应用场景

在Node.js的Express框架中,处理客户端发送到服务器的数据时,我们主要使用req.body、req.params和req.query三个属性。这些属性虽然都是请求对象(req)的一部分,但它们的数据来源和用途却各不相同。本文将为大家详细解读它们的区别和使用方法。…

AI绘画SD中 ControlNet 组件 IP-Adapter 实现风格迁移,AI绘画垫图神器!

大家好,我是画画的小强 今天给大家介绍一下AI绘画SD中ControlNet 的 IP-Adapter 组件,该组件可以方便快捷的帮我们对图片的风格进行迁移,简而言之就是可以参考你放置的图片风格来生成其他图片。 它的效果和reference only有点类似&#xff…

LabVIEW 实现用户授权与管理多项测试项目

在使用 LabVIEW 开发测试软件时,用户授权和项目管理是一个重要的功能。为了确保系统安全性、灵活性和可扩展性,可以设计一个用户管理系统,允许管理员增加或减少用户的测试项目权限。以下是一个详细的实现方案,包括用户授权管理、项…

vue字段判断是否可以鼠标悬浮或者点击跳转

通过字段判断是否可以鼠标悬浮展示颜色 是否点击 <span :class"[converBond.stkindustry ! null ? hoverSpan:,]"click"converBond.stkindustry ! null ?goToIndustry(converBond.stkindustryname,converBond.stkindustry):false">{{converBon…

【星地多网融合调度平台】——打造全方位、立体化的应急通信网络

在复杂的应急场景下&#xff0c;信息的快速传递与指挥调度显得尤为重要。星地多网融合调度指挥箱&#xff0c;凭借其强大的多网融合能力&#xff0c;确保了指挥中心与前线救援队伍之间的信息流畅&#xff0c;无论是位置追踪、应急通信&#xff0c;还是全方位视频监控&#xff0…

数仓架构解析(第45天)

系列文章目录 经典数仓架构传统离线大数据架构 文章目录 系列文章目录烂橙子-终生成长社群群主&#xff0c;前言1. 经典数仓架构2. 传统离线大数据架构 烂橙子-终生成长社群群主&#xff0c; 采取邀约模式&#xff0c;不支持付费进入。 前言 经典数仓架构 传统离线大数据架…

细说MCU用单路DAC模块设计和输出锯齿波的实现方法

目录 一、STM32G474RE的DAC模块 二、配置 1.配置DAC 2.选择时钟源和Debug 3.配置系统时钟 三、代码修改 1.启动DAC 2.给DAC的数据输出寄存器赋值 3.运行并观察输出 一、STM32G474RE的DAC模块 有些MCU本身就带有数/模转换器(Digital to Analog Converter,DAC)模块&am…

netty入门-3 EventLoop和EventLoopGroup,简单的服务器实现

文章目录 EventLoop和EventLoopGroup服务器与客户端基本使用增加非NIO工人NioEventLoop 处理普通任务与定时任务 结语 EventLoop和EventLoopGroup 二者大概是什么这里不再赘述&#xff0c;前一篇已简述过。 不理解也没关系。 下面会简单使用&#xff0c;看了就能明白是什么 这…

加速决策过程:企业级爬虫平台的实时数据分析

摘要 在当今数据驱动的商业环境中&#xff0c;企业如何才能在海量信息中迅速做出精准决策&#xff1f;本文将探讨企业级爬虫平台如何通过实时数据分析加速决策过程&#xff0c;实现数据到决策的无缝衔接。我们聚焦于技术如何赋能企业&#xff0c;提升数据处理效率&#xff0c;…

深入分析 Android ContentProvider (三)

文章目录 深入分析 Android ContentProvider (三)ContentProvider 的高级使用和性能优化1. 高级使用场景1.1. 数据分页加载示例&#xff1a;分页加载 1.2. 使用 Loader 实现异步加载示例&#xff1a;使用 CursorLoader 加载数据 1.3. ContentProvider 与权限管理示例&#xff1…

On the Dimensionality of Word Embedding论文解读

基本信息 作者Zi Yindoi10.3115/v1/D14-1162发表时间2018期刊NIPS网址https://arxiv.org/abs/1812.04224 研究背景 1. What’s known 既往研究已证实 词嵌入的一元不变性。 多数的词嵌入算法本质上都是矩阵分解。 2. What’s new 创新点 提出了 Pairwise Inner Product&…

Prometheus配置alertmanager告警

1、拉取镜像并运行 1、配置docker镜像源 [rootlocalhost ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://dfaad.mirror.aliyuncs.com"] } [rootlocalhost ~]# systemctl daemon-reload [rootlocalhost ~]# systemctl restart docker2、…

单片机主控的基本电路

论文 1.复位电路 2.启动模式设置接口 3.VBAT供电接口 4.MCU 基本电路 5.参考电压选择端口 6.SDRAM电路 7.LCD模块电路 8.USB电路 9.按键电路 10.LED电路 11.SD卡电路 12.电量检测电路 13.蓝牙接口通信电路 14.SPI FLASH 电路

Spark实时(四):Strctured Streaming简单应用

文章目录 Strctured Streaming简单应用 一、Output Modes输出模式 二、Streaming Table API 三、​​​​​​​​​​​​​​Triggers 1、​​​​​​​unspecified&#xff08;默认模式&#xff09; 2、​​​​​​​​​​​​​​Fixed interval micro-batches&am…

总结20个Python接单赚钱的平台,兼职月入6000+_让你早日实现财富自由

今天就给大家盘点几个基本入门接私活的资源&#xff0c;让你轻松学python&#xff0c;实现经济独立。 一、Python兼职种类&#xff1a; 接私活刚学会python那会&#xff0c;就有认识的朋友介绍做一个网站的私活&#xff0c;当时接单赚了4K&#xff0c;后又自己接过开发网站后…

vue3+element-plus 实现动态菜单和动态路由的渲染

在 Vue.js 中&#xff0c;使用 Vue Router 管理路由数据&#xff0c;并将其用于渲染 el-menu&#xff08;Element UI 的菜单组件&#xff09;通常涉及以下几个步骤&#xff1a; 定义路由元数据&#xff1a; 在你的路由配置中&#xff0c;为每个路由项添加 meta 字段&#xff0c…

SQL labs-SQL注入(五,使用sqlmap进行cookie注入)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 引言&#xff1a; Cookie 是一些数据, 存储于你电脑上的文本文件中。当 web 服务器向浏览器发送 web 页面时&#xff0c;在连接关闭后&#xff0c;服务端不会记录用户的信息。Cookie…

新形势下职业教育大数据人才培养策略

一、引言 随着信息技术的飞速发展&#xff0c;大数据已成为驱动经济社会变革的关键力量。在新形势下&#xff0c;职业教育作为技术技能人才培养的重要阵地&#xff0c;面临着如何适应大数据时代要求、提升人才培养质量的紧迫任务。当前&#xff0c;职业教育在大数据人才培养方…

【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !

目录 C语言中指针的大小1. 指针大小的基本概念1.1 32位系统1.2 64位系统 2. 指针大小示例2.1 32位系统输出2.2 64位系统输出 3. 指针大小与数据类型无关示例输出示例 4. 跨平台的指针大小示例输出示例 5. 关键点总结5.1 指针大小与平台关系5.2 跨平台编程注意事项 6. 指针大小示…