【Redis知识点总结】(四)——如何保证缓存与数据库中的数据一致性

Redis知识点总结(四)——如何保证缓存与数据库中的数据一致性

  • 更新缓存
  • 删除缓存
    • 先删除缓存后更新数据库
    • 先更新数据库后删除缓存
  • 使用canal
  • 总结

面试会经常遇到这种问题:你们如何保证缓存与数据库中的数据一致性?或者是:你们如果保证缓存与数据库的双写一致性?

其实这种问题本身就很矛盾,使用缓存的目的,本身就是牺牲一定的强一致性,追求性能的提升,如果真有场景是要保证缓存和数据库一致的,就不适合用缓存。但是既然面试官问到了,我们还是要答一下。

在使用缓存的时候,如果发生更新数据库的操作,我们可以删除缓存,也可以更新缓存。

更新缓存

我认为更新缓存是不可取的,原因有二:

  1. 如果这个缓存是个冷门数据,后续都不会查询到,那么这个更新就是多余的,还会占用内存空间
  2. 缓存和数据库都要更新,还要保证一致性,那么只能用事务,这样性能就会下降,违背了使用缓存的初衷

在这里插入图片描述

但是有一种情况是可以使用更新缓存这种策略的,就是一致性要求不高但是更新的数据是一个热点数据,那我们可以在更新数据库后,马上更新缓存,这样就不会因为有缓存缺失而造成大量的请求打向数据库,也就是缓存击穿的问题。即使更新数据库成功了,但是缓存更新失败也没关系,因为前提是一致性要求不高。

在这里插入图片描述

但是面试官问的是缓存与数据库如果保证一致性,那就不符合一致性要求不高的前提,所以更新缓存这种策略就不用考虑了。

删除缓存

再来看下删除缓存这种策略,我们可以先删缓存在更新数据库,也可以先更新数据库再删除缓存。

先删除缓存后更新数据库

先删缓存后更新数据库这种方案,有两种失败的情况:

  1. 删缓存失败
  2. 删缓存成功,更新数据库失败

如果删除缓存失败,那么就返回更新失败,此时数据库与缓存还是一致的;如果删缓存成功,但是更新数据库失败,那么也返回更新失败,此时只是缓存中没数据,下次查询的时候发现缓存缺失,从数据库中查询加载到缓存中,缓存和数据库还是一致的。这样,看起来好像没什么问题。

在这里插入图片描述

但是这种做法有一个潜在的问题,当缓存的数据的并发访问量比较大的时候,有可能出现如下这种情况:

在这里插入图片描述

线程1发起了一个更新操作,而线程2发起了以查询操作,目标都是同一份数据。线程1首先删除了缓存,但是迟迟没有更新数据库成功,而此时线程2进来了,线程2查询缓存发现缓存缺失,于是查询数据库并加载到缓存,但由于线程1没来得及更新数据库,所以此时缓存中的数据是旧的。随后线程1才更新数据库成功,此时缓存与数据库的数据就不一致了。

解决这种问题的办法,就是使用延迟双删的机制:

在这里插入图片描述

线程1在更新数据库成功后,sleep一段时间,然后再次删除缓存,就能把线程2加载到缓存中的旧值给删掉。但是这个sleep的时间长度不好把握,如果时间短了,可能在线程2加载旧值到缓存前,线程1就醒来了,那么缓存中的旧值还是没删,如果时间长了,又会影响数据更新的性能,数据已经更新成功了,但更新结果迟迟未返回。因此,一般情况下这种延时双删的机制也很少使用,进而先删缓存的这种做法也很少使用。

先更新数据库后删除缓存

再来看看先更新数据库,后删除缓存的做法。

如果更新数据库成功,然后删除缓存成功,那么缓存和数据库是能保证一致性的,但是在更新数据库成功之后,在删除缓存之前,其他线程查询到的还是旧值。

在这里插入图片描述

但是缓存不一致只是在更新数据库成功后,删除缓存成功之前的这一段时间内,如果不是要求强一致性的场景,都是可以接受的。

再看一下更新数据库或删除缓存不成功的情况:先更新数据库,如果更新失败了,那么就返回更新失败,此时缓存与数据库中的数据还是一致的;如果更新数据库成功,但是删除缓存失败,那么缓存与数据库的值是不一致。

在这里插入图片描述

但是我们可以给缓存添加一个过期时间,时间到了也会自动删除,那么我们就容忍一段时间的不一致,这种做法适用于一致性要求不高,只需要保证最终一致性的场景。

在这里插入图片描述

但是如果一致性要求非常高,不能容忍这一段时间的数据不一致,那么我们可以把要删除的key添加到队列中,然后起一个线程异步监听该队列,不断重试删除缓存,直到成功为止。

在这里插入图片描述

这种做法虽然还是没有保证绝对的一致性,但是容忍的不一致性时间更短了,更适合一致性要求较高的场景,但是引入了异步线程和队列,复制度就提升了。

如果再变态一点的,就是要保证绝对的一致,不能容忍一点点的不一致,并且连更新数据库成功后删除缓存成功之前的这一段时间的不一致也不能容忍,那么只能通过事务去保证。

在这里插入图片描述

但是这样一来,性能就会急剧下降,违背了使用缓存的初衷。

使用canal

还有一种保证缓存与数据库一致性的方案就是使用canal。

canal是基于MySQL的binlog日志进行数据同步的一个工具,它伪装成MySQL主从同步中的一个Slave节点,向MySQL主节点发起binlog同步的请求,接收到binlog后,canal就可以把接收到的binlog进行解析与处理,然后把数据同步到下游。

我们配置canal监听MySQL的binlog,然后同步数据到redis,这样就可以实现缓存与数据库的一致性。

在这里插入图片描述

当然,这种方案也不是强一致的,因为从数据库写成功,dump出binlog,到canal成功同步到redis,是存在一点点时差的。

总结

最后总结一下比较靠谱的几种做法。

如果是强一致性的场景,是不适合使用缓存的,那么最好就不要使用缓存了。如果遇到了即要求强一致性,又追求高性能的场景,那就太变态了。

如果是对一致性要求较高,但又不是强一致性,那可以使用canal同步的方案,正常情况下canal读取binlog同步数据到redis这个过程,处理是非常快的。

如果对一致性要求不高,可以采用先更新数据库,后删除缓存的机制,并且在查询数据库加载数据到缓存时,给缓存设置一个过期时间。这样,即使出现不一致,也只需容忍一段时间的不一致,等缓存过期时间到,缓存就会失效,可以到达最终一致性。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小白必看的Python基础之函数篇

函数最重要的目的是方便我们重复使用相同的一段程序。 将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句。 函数的定义 首先,我们要定义一个函数, 以说明这个函数的功能…

把软件加入开机自启动

注意这个方法最佳效果是适用于打开软件后,关闭窗口不会停止服务 例如 nginx 1.把nginx的快捷方式放到如图所示的文件夹下 C:\Users\KIA_27\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 注意KIA_27应改为你自己的用户名

从政府工作报告探计算机行业发展——探索计算机行业发展蓝图

目录 前言 一、政策导向与行业发展 (一)政策导向的影响 (二)企业如何把握政策机遇推动创新发展 二、技术创新与产业升级 三、数字经济与数字化转型 四、国际合作与竞争态势 五、行业人才培养与科技创新 (一&a…

KubeSphere集群安装-nfs分布式文件共享-对接Harbor-对接阿里云镜像仓库-遇到踩坑记录

KubeSphere安装和使用集群版 官网:https://www.kubesphere.io/zh/ 使用 KubeKey 内置 HAproxy 创建高可用集群:https://www.kubesphere.io/zh/docs/v3.3/installing-on-linux/high-availability-configurations/internal-ha-configuration/ 特别注意 安装前注意必须把当前使…

【十】【算法分析与设计】滑动窗口(1)

209. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [nums(l), nums(l1), ..., nums(r-1), nums(r)] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 …

[嵌入式系统-40]:龙芯1B 开发学习套件 -10-PMON启动过程start.S详解

目录 一、龙芯向量表与启动程序的入口(复位向量) 1.1 复位向量: 1.2 代码执行流程 1.3 计算机的南桥 VS 北桥 二、PMON代码执行流程 三、Start.S详解 3.1 CPU初始化时所需要的宏定义 (1)与CPU相关的一些宏定义…

[游戏开发][UE5.3]GAS学习心得

GAS(GameplayAbilitySystem) UE提供的一套技能框架,这个框架也不是万能的,甚至各个部件你要进行封装开发,但这也比你从头写一套技能框架要容易很多。 GAS功能极其强大,所以它是一个庞大的系统,如果想运用得当&#x…

深度学习pytorch——基本运算(持续更新)

基本运算——加、减、乘、除 建议直接使用运算符,函数和运算符的效果相同 代码演示: #%% # 加减乘除 a torch.rand(3,4) b torch.rand(4) # 这里a、b可以相加,别忘了pytorch的broadcast机制 print(ab) print(torch.add(a,b)) print(torc…

MySQL中的索引失效情况介绍

MySQL中的索引是提高查询性能的重要工具。然而,在某些情况下,索引可能无法发挥作用,甚至导致查询性能下降。在本教程中,我们将探讨MySQL中常见的索引失效情况,以及它们的特点和简单的例子。 1. **索引失效的情况** …

代码算法训练营day9 | 28. 实现 strStr() 、459.重复的子字符串

day9: 28. 实现 strStr()KMP的主要应用:什么是前缀表:前缀表是如何记录的: 如何计算前缀表:构造next数组:1、初始化2、处理前后缀不相同的情况3、处理前后缀相同的情况 代码: 459.重复的子字符串…

Python入门(三)

序列 序列是有顺序的数据集合。序列包含的一个数据被称为元素,序列可以由一个或多个元素组成,也是可以没有任何元素的空序列。 序列的类型 元组(定值表):一旦建立,各个元素不可再更变,所以一…

Linux文件操作

pwd命令 cd命令 ls命令 mkdir命令 同时创建父子目录 cp命令 mv命令(相当于用cp复制之后,把源文件删除) 用mv命令来冲命令 rm命令 可以看到,我们用当前目录的文件覆盖了目标路径上的文件,并且目标路径中多了一个以波浪…

5 张图带你了解分布式事务 Saga 模式中的状态机

大家好,我是君哥。 状态机在我们的工作中应用非常广泛,今天聊一聊分布式事务中间件 Seata 中 Saga 模式的状态机。 1 状态机简介 状态机是一个数学模型,它将工作中的运行状态和流转规则抽象出来,可以协调相关信号来完成预先设定…

构造-析构-拷贝构造-赋值运算符重载-const成员函数

1. 类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么时候都不写时,编译器会自动生成以下6个成员函数。 默认成员函数:用户没有显式实现,编译器…

C++之deque与vector、list对比分析

一.deque讲解 对于vector和list,前一个是顺序表,后一个是带头双向循环链表,前面我们已经实现过,这里就不再讲解了,直接上deque了。 deque:双端队列 常见接口大家可以查看下面链接: deque - …

STM32第九节(中级篇):RCC(第一节)——时钟树讲解

目录 前言 STM32第九节(中级篇):RCC——时钟树讲解 时钟树主系统时钟讲解 HSE时钟 HSI时钟 锁相环时钟 系统时钟 SW位控制 HCLK时钟 PCLKI时钟 PCLK2时钟 RTC时钟 MCO时钟输出 6.2.7时钟安全系统(CSS) 小结 前言 从…

单链表操作

单链表操作 1. 链表的概念2. 链表的分类2.1.单向或者双向2.2 带头或者不带头2.3 循环或者非循环2.4 常用的链表 3. 单链表的实现3.1 单链表的打印3.2 单链表的头插3.3 单链表的尾插3.4 单链表的头删3.5 单链表的尾删3.6 单链表的查询3.7 在pos前插入数据3.8 在pos后插入数据3.9…

Linux——进程通信(一) 匿名管道

目录 前言 一、进程间通信 二、匿名管道的概念 三、匿名管道的代码实现 四、管道的四种情况 1.管道无数据,读端需等待 2.管道被写满,写端需等待 3.写端关闭,读端一直读取 4.读端关闭,写端一直写入 五、管道的特性 前言 …

不锈钢多功能电工剥线钳分线绕线剪线剥线钳剥线压线扒皮钳子

品牌:银隆 型号:089B绿色 材质:镍铬钢(不锈钢) 颜色分类:089B灰色,089B红色,089B绿色,089B黑色,089B橙色 功能齐集一身,一钳多用,多功能剥线钳。剥线,剪线&#xff…

Java-CAS 原理与 JUC 原子类

由于 JVM 的 synchronized 重量级锁涉及到操作系统(如 Linux) 内核态下的互斥锁(Mutex)的使用, 其线程阻塞和唤醒都涉及到进程在用户态和到内核态频繁切换, 导致重量级锁开销大、性能低。 而 JVM 的 synchr…