如何保证Redis和数据库的数据一致性

news2024/11/24 16:35:37

文章目录

  • 0. 前言
  • 1. 补充知识:CP和AP
  • 2. 什么情况下会出现Redis与数据库数据不一致
  • 3. 更新缓存还是删除缓存
  • 4. 先操作缓存还是先操作数据库
    • 4.1 先操作缓存
      • 4.1.1 数据不一致的问题是如何产生的
      • 4.1.2 解决方法(延迟双删)
      • 4.1.3 最终一致性和强一致性
      • 4.1.4 如何确定延迟双删的延迟时间
    • 4.2 先操作数据库(推荐使用)
      • 4.2.1 数据不一致的问题是如何产生的
      • 4.2.2 解决方法(删除+延迟删除)
  • 5. 删除缓存失败的情况
      • 5.1 删除重试机制
      • 5.2 canal
      • 5.3 引入canal后的流程
  • 6. 总结

阅读本文前可以先阅读我的另一篇博文: Windows环境下安装Redis并设置Redis开机自启

0. 前言

在面试的时候,如果面试官看到我们有处理高并发项目的经验,并且在项目中用到了 Redis,面试官通常都会问 Redis 缓存怎么跟数据库保持一致,我们一起来探讨一下这个问题

1. 补充知识:CP和AP

在分布式系统的一致性模型中,CP 和 AP 是 CAP 定理中的两个关键概念

CAP 定理,也称为布鲁尔定理(Brewer’s Theorem),是由计算机科学家埃里克·布鲁尔(Eric Brewer)在 2000 年提出的

CAP 定理描述了分布式系统在设计时面临的三个基本属性,即一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),并指出分布式系统在任何给定时间只能同时满足其中的两个属性


以下是 CP 和 AP 的含义:

  1. CP(Consistency and Partition Tolerance)
    • 一致性(Consistency):指所有节点看到的数据是一致的,即更新操作在所有节点上要么全部成功,要么全部失败
    • 分区容错性(Partition Tolerance):指系统在出现网络分区(即网络中的一部分节点无法与其他节点通信)的情况下仍然能够继续运行
    • CP系统在发生网络分区时,会选择一致性和分区容错性,可能会牺牲可用性。这意味着在分区发生时,系统可能会拒绝某些操作以保证数据的一致性
  2. AP(Availability and Partition Tolerance)
    • 可用性(Availability):指系统在面对客户端的请求时,总是能够给出响应,即使是在部分节点失败或网络分区的情况下
    • 分区容错性(Partition Tolerance):系统在出现网络分区的情况下仍然能够继续运行
    • AP系统在发生网络分区时,会选择可用性和分区容错性,可能会牺牲一致性,这意味着系统在分区发生时,仍然可以响应客户端的请求,但可能会返回不一致的数据

总结来说,CP 和 AP 是 CAP 定理中描述的两种不同的设计选择,它们反映了分布式系统在不同场景下的权衡

选择 CP 还是 AP 取决于具体应用的需求,例如,金融系统通常需要强一致性,因此会选择CP,而社交媒体或某些类型的缓存系统可能会选择 AP 以提供更高的可用性

2. 什么情况下会出现Redis与数据库数据不一致

我们先来看一下使用 Redis 读取数据的场景

在这里插入图片描述

当客户端发起一个查询数据的请求时,会先检查 Redis 中检查有没有对应的数据,有的话直接返回,没有的话就会查询数据库,把从数据库中查询到的数据保存到 Redis 中后,再返回给客户端

在将数据库中查询到的数据保存到 Redis 时,一般会为数据设置一个过期时间,主要目的是为了避免一些冷数据一直占用 Redis 的空间

如果只进行读操作,是不会出现数据不一致的情况的,只有读操作和写操作同时进行,才会出现数据不一致的情况


我们再来看一下写数据的场景,写数据的场景就比较多了:

  • 先更新缓存再更新数据库
  • 先删除缓存再更新数据库
  • 先更新数据库再更新缓存
  • 先更新数据库再删除缓存

总结起来,就是以下两点区别:

  1. 更新缓存还是删除缓存
  2. 先操作缓存还是先操作数据库

3. 更新缓存还是删除缓存

我们是更新 Redis 中对应的数据,还是直接删除 Redis 中对应的数据呢

推荐使用删除 Redis 中对应的数据的方式,为什么呢

因为删除的逻辑非常简单,删除缓存之后 Redis 中就没有对应的数据了,等到下一个线程进行查询操作时,会从数据库中查询数据,接着将查询到的数据保存到 Redis 中

如果是更新 Redis 中的数据,可能会涉及到一系列复杂的业务逻辑计算,整个更新操作所需要付出的成本是比删除操作更高的

4. 先操作缓存还是先操作数据库

到底是先操作缓存好呢,还是先操作数据库好呢

当出现数据不一致的时候,这两种方案是怎么处理的呢,我们分别探讨一下

4.1 先操作缓存

4.1.1 数据不一致的问题是如何产生的

我们先来看一下比较简单的先操作缓存的场景

在这里插入图片描述

当读操作和写操作并发执行的时候,数据不一致的问题是如何产生的呢

在这里插入图片描述

首先,线程 1 发起了一个修改数据的请求,线程 1 删除缓存中的对应数据,接着去修改数据库,但是线程 1 在修改数据库时出现了网络延迟,在线程 1 修改数据库前,线程 2 发起了一个查询请求,由于线程 1 把缓存中对应的数据删掉了,线程 2 在 Redis 中找不到对应的数据,线程 2 会从数据库中查询数据,并将查询到的数据保存到 Redis 中

但线程 2 查询到的是一个旧数据,因为线程 1 还没有将的数据保存到数据库中,当线程 1 成功将新数据保存到数据库之后,就出现了数据不一致的情况(数据库中的是新数据,Redis 中的是旧数据)

线程 1 成功将新数据保存到数据库之后,如果有大量的查询请求,那么查到的数据都是 Redis 中的旧数据,只有等到旧数据过期了,才能查到数据库中的新数据

4.1.2 解决方法(延迟双删)

怎么解决呢,其实也比较简单

我们先重演一遍出现数据不一致的过程,当线程 1 成功将新数据保存到数据库之后,我能不能再删除一次缓存呢,当然是可以的,这也就是我们平时经常听到的延迟双删

将新数据保存到数据库之后再次删除缓存,如果后面又有查询请求,因为 Redis 中没有对应的数据,会从数据库中查询数据,并将查到的数据保存到 Redis 中,这样做就可以保证 Redis 和数据库的数据一致性

4.1.3 最终一致性和强一致性

但是在线程 1 成功将新数据保存到数据库之前,线程 2 查询到的数据依然是旧数据,会出现一次数据不一致的情况

有同学可能会说,能不能把这一次数据不一致也避免掉,当然可以,不过要引入强一致性的概念,如果要求 Redis 中的数据和数据库中的数据保持强一致性的话,就需要确保操作缓存操作和操作数据库操作满足原子性

但 Redis 和数据库一般是在不同的服务器上的,需要两步操作(即使是在同一台服务器上也需要两步操作),如果要保证同时进行两步操作的原子性,就需要借助锁了

但是加锁会影响我们整个系统的吞吐量,想一下,我们用 Redis 的目的是什么,是不是为了提高系统的性能,如果为了强一致性而去加锁,是不是就得不偿失了

所以说,一致性跟可用性,我们只能满足一个,在可用性的基础上,我们可以使用刚才提到的成功保存数据到数据库之后,再次删除缓存中对应的数据的策略,虽然会出现少量数据不一致的情况,但是 Redis 和数据库是保持数据的最终一致性的

我们保证 Redis 和数据库的数据一致性,一般是采用最终一致性,而不是强一致性,因为强一致性会影响系统的吞吐量

4.1.4 如何确定延迟双删的延迟时间

但我们得注意一点,上面提到的双删策略(操作数据库前删除一次缓存,操作数据库后又删除一次缓存),在第二次删除的时候,要延迟删除

在这里插入图片描述

为什么要这么做呢,我们重演一下发生数据不一致的过程,当线程 1 删除缓存之后,线程 2 发起查询请求,发现 Redis 中没有对应的数据,从数据库中查询数据,在线程 2 将查询到的数据之前,线程 1 成功将新数据保存到数据库并删除缓存,在线程 1 删除缓存之后,线程 2 才把查询到的数据放入到 Redis 中,造成了数据不一致的情况

所以,我们要延迟一定时间之后再进行删除,那怎么确定延迟时间呢,我们需要自行评估项目的读取数据业务的耗时(即线程 2 从数据库读取数据到写入缓存的整个过程的总耗时),防止线程 2 将旧数据存到 Redis 中

4.2 先操作数据库(推荐使用)

4.2.1 数据不一致的问题是如何产生的

我们先来看一下比较简单的先操作数据库的场景

在这里插入图片描述

客户端发起一个修改数据的请求,先将修改保存到数据库中,再删除缓存


当读操作和写操作并发执行的时候,数据不一致的问题是如何产生的呢

在这里插入图片描述

线程 1 操作数据库,将新数据保存到数据库中,在线程 1 删除缓存之前,线程 2 发起查询请求,那么线程 2 查询到的就是旧数据,等到线程 1 删除缓存之后,下一个线程才能查询到新数据

先操作数据库,再删除缓存,能保证数据的最终一致性,实现起来也比较简单,所以更推荐大家先操作数据库,再删除缓存

只不过先操作数据库,在线程 1 删除缓存之前,其它线程查询到的是脏数据,但是能保证数据的最终一致性


其实,先操作数据库也有可能会出现数据最终一致性被破坏的情况,我们来模拟一下这个过程,当线程 2 发起查询请求时,缓存中对应的数据刚好过期了,线程 2 从数据库中查找数据,在线程 2 将数据写入缓存之前,线程 1 完成了更新数据库并删除缓存的操作,接着线程 2 才将数据写入缓存,此时数据库中存的是新数据,缓存中存的是旧数据,数据最终一致性被破坏


另外,在数据库做了集群的情况下,先操作数据库也有可能导致数据的最终一致性被破坏的情况

在数据库集群中,一般是主节点负责写操作,从节点负责读操作,在高并发场景下,更新主库的数据并删除缓存之后,如果从库没来得及同步更改,后续的查询请求就会从数据库的从库中查找数据,并将数据保存到缓存中,但从库中的数据是旧数据,从而导致数据的最终一致性被破坏

4.2.2 解决方法(删除+延迟删除)

该怎么解决这个问题呢,跟前面提到的延迟双删思想类似,更新数据库并删除缓存之后,再延迟删除一次缓存,这样就能保证第二次删除缓存后查到的数据都是新数据

当然,这个延迟时间需要自行评估项目的读取数据业务的耗时,如果延迟时间过短,还是会出现数据不一致的情况

但频繁删除缓存有可能会导致缓存击穿的问题,也是比较严重的(至于什么是缓存击穿,可以参考我的另一篇博文:Redis缓存面试三兄弟:缓存穿透、缓存雪崩、缓存击穿)


先操作缓存中提到的延迟双删的过程:删除缓存→更新数据库→延迟删除缓存

此处的双删:更新数据库→删除缓存→延迟删除缓存

5. 删除缓存失败的情况

无论是先操作缓存还是先操作数据库,都有可能出现删除缓存失败的情况,当然,这种情况比较极端

如果删除缓存失败了,后续线程查询到的全都是旧数据,必须等待缓存中对应的数据过期了之后才能查到新数据

5.1 删除重试机制

针对这种删除失败的情况,我们可以借助消息队列,并采用删除重试机制,比如重试 3 次,如果重试 3 次后仍然失败,则记录日志到数据库并发送警告给相关人员,进行人工介入

高并发场景下,重试最好采用异步的方式

当缓存删除失败之后,发送一个异步消息到消息队列中,让系统监听消息队列,一旦发现 Redis 的某个 key 删除失败了,就执行删除重试操作,这样能在一定程度上避免删除失败所引起的数据不一致的情况

在这里插入图片描述

但是,当我们加入了删除重试的代码之后,有什么缺点呢

可以发现,加入删除重试的代码之后,业务代码的耦合度太高了,要实现解耦的话,可以采用另一个组件——canal

值得注意的是,引入 canal 之后,系统的复杂度也会提升,毕竟 canal 是一个新的中间件,需要监控 canal 的运行状态,保证 canal 运行正常

5.2 canal

canal 的官网:canal(阿里巴巴开源的一个组件)


Canal 是一个基于 MySQL 数据库增量日志解析的开源数据同步工具,主要用于解决数据库间的数据同步问题,特别是在大数据场景下,Canal 可以帮助用户将 MySQL 数据库中的数据实时同步到其他数据存储系统中,如 Elasticsearch、HBase、Kafka 等

在这里插入图片描述

Canal 的工作原理主要依赖于 MySQL 的主从复制机制,以下是 Canal 实现数据同步的基本步骤和原理(人工智能给出的回答,仅供参考):

  1. MySQL 主从复制原理
    • MySQL 支持主从复制功能,其中主库(Master)上的所有写操作都会记录到二进制日志(Binary Log,简称 binlog)中
    • 从库(Slave)通过一个 I/O 线程连接到主库,请求主库的 binlog
    • 主库将 binlog 发送给从库,从库的 I/O 线程将 binlog 写入到本地的中继日志(Relay Log)
    • 从库的 SQL 线程读取中继日志,并执行日志中的写操作,从而实现数据的复制
  2. Canal 的工作流程
    • 模拟 Slave:Canal 模拟 MySQL 从库的行为,与主库建立连接,并请求主库的 binlog
    • 解析 binlog:当主库有数据变更时,Canal 会接收到相应的 binlog 事件。Canal 使用自己的 binlog 解析引擎来解析这些事件,并将其转换为更容易理解的格式
    • 事件投递:解析后的数据变更事件可以被投递到其他系统,如消息队列(例如 Kafka)或者直接写入到目标存储系统(例如 Elasticsearch)
  3. 关键组件
    • Canal Server:运行 Canal 服务,负责从 MySQL 中读取 binlog,解析并投递数据变更事件
    • Canal Client:负责从 Canal Server 获取数据变更事件,并将其应用到目标系统
  4. 细节说明
    • 位置记录:Canal 需要记录每次同步的位置,以便在服务重启后能够从上次停止的位置继续同步
    • 数据过滤:Canal 支持配置过滤规则,只同步特定数据库或表的数据
    • 数据格式转换:Canal 可以将解析后的 binlog 事件转换为 JSON、XML 等格式

Canal 能够实现 MySQL 数据库与其他数据存储系统之间的实时数据同步,广泛应用于数据备份、数据迁移、数据集成和实时数据处理等场景

5.3 引入canal后的流程

简单地来说,当 MySQL 中出现了数据变动,canal 能够立马感知到,并通知 canal 的客户端

canal 的客户端有很多种,我们可以使用 SpringBoot 应用来充当 canal 的客户端,接收来自 canal 的通知,一旦数据库发生了数据修改,数据库的主节点会通知 canal,canal 再去通知 canal 的客户端

也就是说,更新数据库后的删除缓存操作和删除重试中的删除缓存操作都交由 canal 来完成

在这里插入图片描述

6. 总结

  1. 如果是在并发量不高的的场景下,采用删除缓存→更新数据库→延迟删除缓存方案或更新数据库→删除缓存方案都是合理的
  2. 如果是在高并发的场景下,无论是哪种方案,即使对方案做了优化,都有可能出现数据不一致的情况,只不过是概率的大小问题
  3. 保证 Redis 和数据库的数据一致性,一般是保证数据的最终一致性,而不是数据的强一致性
  4. 对于读多写少的数据,我们可以放在缓存中,减轻数据库的压力;对于读多写多的数据,放入缓存中弊大于利,因为放入缓存中的数据应该是对一致性要求没有那么高的数据
  5. 如果数据要求强一致性,就需要借助锁来确保更新数据库操作和删除缓存操作的原子性,但引入锁会降低系统的吞吐量,使用缓存本来就是为了提高系统的性能,引入锁反而是得不偿失
  6. 一致性和可用性往往不可兼得,需要根据实际选择符合业务场景的方案

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

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

相关文章

【大数据算法】一文掌握大数据算法之:大数据算法分析技术。

大数据算法分析技术 1、引言2、 大数据分析技术2.1 时间/空间复杂度2.2 I/O 复杂度2.3 结果质量2.4 通信复杂度 3、总结 1、引言 小屌丝:鱼哥,最近更文有些不频繁了哈。 小鱼:这一个月不见,你这说话方式也变了。 小屌丝&#xff…

C++与C语言的排序算法对比(插入,希尔,归并)

1. 引言 排序算法是计算机科学中的基础概念,广泛应用于数据处理和算法设计中。本文将通过插入排序、希尔排序、归并排序和选择排序这四种常见的排序算法,分别用C和C语言实现,并对它们进行优劣对比,以帮助读者更好地理解这两种语言…

MATLAB支持的字体

listfonts 列出可用的系统字体 {Adobe Devanagari } {Agency FB } {Algerian } {AlienCaret } {AMS } {Arial } {Arial Black …

[LeetCode] 21. 合并两个有序链表

题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 2: 输入:l1 [], l2 […

数据结构(8.3_1)——冒泡排序

交换排序: 冒泡排序和快速排序 冒泡排序: 示例: 从行往前将A[i-1]和A[i]比较若遇到A[i-1]>A[i]则将两个元素交换 注意: 代码实现: //交换 void swap(int& a, int& b) {int temp a;a b;b temp; } //冒…

入门!Linux 常见指令及权限管理全面指南

Linux 操作系统在现代计算机应用中扮演着重要的角色,广泛用于服务器、桌面系统、嵌入式设备及云计算平台等领域。理解和掌握 Linux 常见指令及权限管理机制,是每一位系统管理员和开发人员的基础技能。本文将详细介绍 Linux 系统的基本背景、常用指令、权…

设计模式概览

设计模式是一种解决常见编程问题的经验总结,提供了代码的可重用性、可扩展性和可维护性。常见的设计模式有23个,主要分为三大类:创建型模式、结构型模式和行为型模式。下面是这三类设计模式的详细分类和讲解: 一、创建型模式 创建…

进入 Searing-66 火焰星球:第一周游戏指南

Alpha 第四季已开启,穿越火焰星球 Searing-66,带你开启火热征程。准备好勇闯炙热的沙漠,那里有无情的高温和无情的挑战在等待着你。从高风险的烹饪对决到炙热的冒险,Searing-66 将把你的耐力推向极限。带上充足的水,天…

Fusion创建一个简单的api脚本文件

我的Fusion版本:Fusion 2.0.20476 x86_64 脚本模块在实用程序->附加模型->脚本和附加模块,快捷键为shifts 里面有一些演示脚本,可以直接使用 也可以自己创建一个新的脚本 创建的脚本在此处—— 选择脚本文件,点击编辑&a…

小新学习Docker之Ansible 的脚本 --- playbook 剧本

一、playbook 剧本简介 playbooks 本身由以下各部分组成: (1)Tasks:任务,即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 (2)Variables:变量 (3…

Linux系统基础-动静态库

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 Linux系统基础-动态库和静态库 收录于专栏[Linux学习] 本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 1. 动…

如何在 Jupyter Notebook 执行和学习 SQL 语句(下)—— 进阶版题目综合(多表连接,窗口函数,子查询等等)

这是我收藏的一些相关的题目,其中包含基本操作(如创建表、插入数据)(注意一般企业也不会让删除数据啥的,毕竟刚进去哪会让对人家数据库做什么操作,我是实习的时候参加了数据仓库的建设,插了一些…

498.对角线遍历

目录 题目解法代码说明:输出: 如何确定起始点?解释一下max(0,d−m1)是什么意思? 如何遍历对角线?.push_back是怎么用的? 题目 给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序&#xf…

JNA调用c++动态库返回数据

jna学习网站 JNA Examples 1、返回String, pch.h头文件 // pch.h: 这是预编译标头文件。 // 下方列出的文件仅编译一次,提高了将来生成的生成性能。 // 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。 // 但是,如果此处…

软考攻略/超详细/系统集成项目管理工程师/基础知识分享18

6.5数据分析及应用 6.5.1 数据集成(掌握) 数据集成就是将驻留在不同数据源中的数据进行整合,向用户提供统一的数据视图,使得用户能以透明的方式访问数据。 WebServices技术是一个面向访问的分布式计算模型,它的本质是…

Nature 正刊丨空间蛋白质组学确定JAKi是一种致命皮肤病的治疗方法

01摘要 中毒性表皮坏死松解症(TEN)是一种由常见药物引发的致命药物性皮肤反应,是一个新出现的公共卫生问题1,2,3。TEN患者会因角质形成细胞死亡而发生严重和突然的表皮脱离。尽管已经提出了驱动角质形成细胞死亡的分子机制,但主要…

10.21 IO进程直接的通信

1、用消息队列实现两个进程直接的通信 先输入的代码 #include<myhead.h> //定义消息列队的结构体 struct msgbuf {long mtype; //消息类型char mtext[1024]; //消息正文 };//宏定义正文的大小 #define MSGSZ (sizeof(struct msgbuf) - sizeof(long))//定义回收僵尸信号…

Linux第一讲:Linux基本指令

Linux第一讲&#xff1a;Linux基本指令 1.Linux发展历史2.先快速了解一些指令3.指令 -- ls3.1知识点1 -- 文件属性3.1.1详细解析ls指令3.1.1.1 ls3.1.1.2 ls -l 、ls -a3.1.1.2.1 ls -l3.1.1.2.2 ls -a3.1.1.2.3 ls -la 3.1.1.3其它指令 3.2知识点2 -- 什么是.和..3.3知识点3 -…

手写模拟Spring的基本功能

文章目录 1. Spring的基本功能2. 容器启动 容器启动&#xff0c;即创建容器对象并赋予配置对象3. BeanDefinition扫描4. Bean的生命周期5. 单例Bean与多例Bean6. 依赖注入7. AOP8. Aware 回调9. 初始化10. BeanPostProcessor附录&#xff1a; 1. Spring的基本功能 2. 容器启动 …

【鸡翅Club】项目启动

一、项目背景 这是一个 C端的社区项目&#xff0c;有博客、交流&#xff0c;面试学习&#xff0c;练题等模块。 项目的背景主要是我们想要通过面试题的分类&#xff0c;难度&#xff0c;打标&#xff0c;来评估员工的技术能力。同时在我们公司招聘季的时候&#xff0c;极大的…