【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

这就是我们经常可用听到的“缓存双删”、“延迟双删除”等词。

        这种方法看起来好像真的可以解决缓存不一致的问题,但是对于这个延迟删除缓存,我们到底该什么时间删呢?

以下有几个可供参考的方法:

  1. 定时任务: 可以在应用程序中设置一个定时任务,在第一次删除缓存后延迟指定的时间,然后执行第二次删除缓存的操作。呃,这个说白了就是让请求A 的最后一次删除缓存操作等待一段时间再去执行。
  2. 异步处理: 在更新数据库成功后,启动一个异步任务(例如使用 RabbitMQ、Kafka 等消息队列或者线程池),该任务在延迟指定的时间后执行第二次删除缓存的操作。
  3. Redis过期机制:设置Redis键的过期时间(TTL)为一个较短的值,这样即使在第二次删除之前有新请求将旧数据写入缓存,也会在短时间内自动过期并被删除。

对于第一个方法,可能是一个“好用”,但不实用的方法,这种只需要一个sleep xxx ms,就能完成的功能,可不就是好用吗(doge)。说它不实用是因为它不可控,具体的就不说了。

然而,尽管存在这些不可控因素,定时任务仍然是实现延迟双删等策略的一种常见方法。毕竟哪有那么多的高并发的场景啊,还有就是我们程序员“懒”,功能实现了就行了

异步处理这个方法感觉是以上几个方法中,较好的,实现了功能的同时还保证了稳定。但是麻烦。。。因为需要添加一个新的组件。

下面是这个方法需要注意的几个点:

  1. 定义异步任务:首先,你需要定义一个异步任务,这个任务负责在数据库更新后执行第二次删除Redis缓存的操作。这个任务可以是一个实现了特定接口或者继承了特定类的类,具体取决于你使用的编程语言和框架。

  2. 更新数据库并触发异步任务:当需要更新数据库时,首先执行第一次删除Redis缓存的操作,然后进行数据库的更新。在数据库更新成功后,立即触发定义好的异步任务。这通常可以通过以下方式实现:

    • 使用消息队列(如RabbitMQ、Kafka等):将一个消息发送到队列中,消息的内容包含了需要删除的缓存键或者其他相关的信息。然后,有一个消费者服务监听这个队列,接收到消息后执行第二次删除缓存的操作。
    • 使用线程池或者异步执行框架:直接在应用程序中创建一个新的线程或者任务,将其提交到线程池或者异步执行框架中。这个新任务会在后台运行,并在延迟指定的时间后执行第二次删除缓存的操作。
  3. 实现异步任务逻辑:在异步任务的实现中,需要包含以下逻辑:

    • 延迟执行:根据业务需求,设置一个合适的延迟时间。这个时间应该足够长,以覆盖从第一次删除缓存到数据库更新完成之间的时间窗口,但又不能太长,以免影响数据的一致性。
    • 删除缓存:在延迟时间过后,执行第二次删除Redis缓存的操作。这通常涉及到与Redis服务器的通信,使用适当的Redis客户端库来发送删除命令。
  4. 处理异常和重试:在异步任务的执行过程中,可能会遇到各种异常情况,如网络中断、Redis服务器故障等。为了保证数据的一致性和系统的稳定性,需要实现适当的异常处理和重试机制。例如,如果删除缓存失败,可以尝试重新发送删除命令,或者记录错误日志并通知运维人员。

具体消息对列的使用,等之后再说吧。

 对于第三种方法,我感觉还没第一种好,他的一下问题如下:

  1. 数据一致性问题:延迟双删策略的主要目的是确保在更新数据库后,能够及时删除相关的Redis缓存,以避免数据不一致。而Redis的过期机制是基于时间的,它不能精确地与数据库更新操作同步,因此可能会导致在键过期和实际删除之间的时间窗口内,新的请求查询到旧的数据库数据并将其放入缓存。
  2. 过期时间设置复杂性:为了使用过期机制替代延迟双删,需要为每个缓存键设置一个合适的过期时间,这个时间需要考虑到数据库更新的频率、网络延迟、系统负载等因素,设置起来可能会比较复杂和难以准确预测。
  3. 内存管理问题:如果大量缓存键在同一时间过期,可能会引发Redis的内存管理和垃圾回收压力,影响系统的性能和稳定性。
  4. 无法应对异常情况:如果在键过期和实际删除之间发生系统故障或者网络中断等情况,可能会导致数据不一致或者缓存失效的问题。

 扯远了,回到正轨。。

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”。

 

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

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

相关文章

ClickHouse--10--临时表、视图、向表中导入导出数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1.临时表1.1 特征1.2 创建一个临时表 2.视图2.1 普通视图2.2 物化视图 3.向表中导入导出数据3.1 案例 1.临时表 1.1 特征 ClickHouse 支持临时表,临时表…

智能汽车行业产业研究报告:毫米波雷达优势明显,核心壁垒是芯片、天线阵列、波形设计

今天分享的是智能汽车系列深度研究报告:《智能汽车行业产业研究报告:毫米波雷达优势明显,核心壁垒是芯片、天线阵列、波形设计》。 (报告出品方:国泰君安证券) 报告共计:67页 毫米波雷达被广泛…

Flume(二)【Flume 进阶使用】

前言 学数仓的时候发现 flume 落了一点,赶紧补齐。 1、Flume 事务 Source 在往 Channel 发送数据之前会开启一个 Put 事务: doPut:将批量数据写入临时缓冲区 putList(当 source 中的数据达到 batchsize 或者 超过特定的时间就会…

【前端web入门第六天】01 CSS浮动

⭐️第六天目标 解决布局问题如多个div标签在同一行的问题 简单来说,就是可以两个标签,一个在左边,另一个在右边. 👉相关知识 标准流浮动flex布局❗️ ❗️ ❗️ 标准流是先导,浮动和flex布局都可以解决问题,但是浮动在目标开发领域较为落后,主流的解决办法是flex…

OpenCV Mat实例详解 一

OpenCV中的Mat是一个类,它用存储图像信息。由两部分数据组成:矩阵头和像素值矩阵。矩阵头包含矩阵尺寸、存储方法、存储地址等信息,而像素值矩阵则存储实际的像素值数据。 Mat类在OpenCV中有十分重要的作用,图像信息的载入、保存、…

UI设计常见风格(1):一文读懂九个,教你如何辨识。

Hello,我是大千UI工场,设计风格是我们新开辟的栏目,上次讲了毛玻璃风格、辨识方法、应用场景、运用方法等,很受大家欢迎,本次带来常见的风格及辨识,让大家有个总览,以后会逐个讲解的&#xff0c…

【Java】零基础蓝桥杯算法学习——动态规划例题

例题:2023年第十四届蓝桥杯Java软件开发B组E题 蜗牛 参考解答: 参考代码示例: import java.util.Scanner; public class Main {static int N 100010;static int[] arr new int[N];static int[] a new int[N]; //传送带的起始坐标static …

【实战】一、Jest 前端自动化测试框架基础入门(一) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(一)

文章目录 一、前端要学的测试课1.前端要学的测试2.前端工程化的一部分3.前端自动化测试的例子4.前端为什么需要自动化测试?5.课程涵盖内容6.前置技能7.学习收获 二、Jest 前端自动化测试框架基础入门1. 自动化测试背景及原理前端自动化测试产生的背景及原理 2.前端自…

开什么店最稳定轻松?适合一个人开的实体店推荐

在创业的道路上,很多人都希望找到一种稳定轻松的开店方式。 作为一名资深的鲜奶吧创业者,我将分享我的经验和见解,希望能给那些想开实体店的朋友们一些启示!! 我开鲜奶吧已经有 5 年时间了,目前经营的是鲜…

MIT-BEVFusion系列八--onnx导出1 综述及相机网络导出

目录 综述export-camera.py加载模型加载数据生成需要导出成 onnx 的模块Backbone 模块VTransform 模块 生成 onnx使用 pytorch 原生的伪量化计算方法导出 camera.backbone.onnx导出 camera.vtransform.onnx 综述 bevfusion的各个部分的实现有着鲜明的特点,并且相互…

Harris关键点检测以及SAC-IA粗配准

一、Harris关键点检测 C #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/common/io.h> #include <pcl/keypoints/harris_3d.h> #include <pcl/visualization/pcl_visualizer.h> #include …

摸索设计模式的魅力:从策略模式看软件设计的智慧-灵活应对变化的艺术

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 一、案例场景1.1 一坨坨代码实现1.2 存在的问题 二、使用策略模式解决问题2.1 使用策略模式重构代码2.2 克服了问题 三、模式讲解3.1 结构图及说明3.2 实现步骤和注意事项3.3 适用场景 四、优势和局限性4.1 优势4.2 局…

JSP原理简述

JSP动态网页技术&#xff0c;可以定义html&#xff0c;css&#xff0c;js等静态内容&#xff0c;还可以定义java代码等动态内容。 注意导入坐标时&#xff0c;JSP的scope标签是provided&#xff0c;和servlet一样&#xff0c;否则会报错。 JSP本质上就是一个Servlet&#xff0c…

微信小程序的疑惑总结

未解决&#xff1a; 1.storebindings 这里的storebindings是什么 2.空行怎么写&#xff1f; 我用这个<text>\n</text>写&#xff0c;在模拟器上好使&#xff0c;在真机上显示\n 解决方法&#xff1a;在组件里写class类名&#xff0c;wxss里面改高度 已解决&am…

GAN生成对抗性网络

一、GAN原理 出发点&#xff1a;机器学习中生成模型的问题 无监督学习是机器学习和未来人工智能的突破点&#xff0c;生成模型是无监督学习的关键部分 特点&#xff1a; 不需要MCMC或者变分贝叶斯等复杂的手段&#xff0c;只需要在G和D中对应的多层感知机中运行反向传播或者…

NetMizer 日志管理系统 多处前台RCE漏洞复现

0x01 产品简介 NetMizer是提供集成应用交付和应用安全解决方案以实现业务智能网络的优秀全球供应商,为全球企业和运营商提供确保关键业务应用的全面可用性、高性能和完善的安全性的解决方案。 0x02 漏洞概述 NetMizer 日志管理系统position.php、hostdelay.php、等接口处存在…

Android---Jetpack Compose学习006

1. 点击 clickable 修饰符允许应用检测对已应用该修饰符的元素的点击。 示例&#xff1a;点击控件&#xff0c;使得内容发生改变 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setCo…

比较不相交5点结构的顺序

( A, B )---6*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有6个节点&#xff0c;AB训练集各由6张二值化的图片组成&#xff0c;让差值结构有5个点&#xff0c;收敛误差7e-4&#xff0c;收敛199次&#xff0c;统计迭代次数平均值并排序。 如果行和列可以自由的变换&#xff0c;5个…

Java中的Stack的基本讲解

目录 一、创建栈 二、Stack的一些常用方法 对于栈的基本概念,我就不细说了&#xff0c;后进先出。比如1,2,3,依次进栈&#xff0c;那么出栈就是3&#xff0c;2&#xff0c;1。 一、创建栈 Stack<引用数据类型&#xff0c;基本数据类使用包装类> snew Stack<>();St…

杂谈--spconv导出中onnx的扩展阅读

Onnx 使用 Onnx 介绍 Onnx (Open Neural Network Exchange) 的本质是一种 Protobuf 格式文件&#xff0c;通常看到的 .onnx 文件其实就是通过 Protobuf 序列化储存的文件。onnx-ml.proto 通过 protoc (Protobuf 提供的编译程序) 编译得到 onnx-ml.pb.h 和 onnx-ml.pb.cc 或 on…