如何保证Redis和数据库的数据同等性

打印 上一主题 下一主题

主题 1040|帖子 1040|积分 3120

阅读本文前可以先阅读我的另一篇博文: 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 的寄义:

  • CP(Consistency and Partition Tolerance)

    • 同等性(Consistency):指全部节点看到的数据是同等的,即更新操纵在全部节点上要么全部乐成,要么全部失败
    • 分区容错性(Partition Tolerance):指系统在出现网络分区(即网络中的一部分节点无法与其他节点通信)的环境下仍旧可以或许继续运行
    • CP系统在发生网络分区时,会选择同等性和分区容错性,大概会牺牲可用性。这意味着在分区发生时,系统大概会拒绝某些操纵以保证数据的同等性

  • AP(Availability and Partition Tolerance)

    • 可用性(Availability):指系统在面临客户端的请求时,总是可以或许给出相应,纵然是在部分节点失败或网络分区的环境下
    • 分区容错性(Partition Tolerance):系统在出现网络分区的环境下仍旧可以或许继续运行
    • AP系统在发生网络分区时,会选择可用性和分区容错性,大概会牺牲同等性,这意味着系统在分区发生时,仍旧可以相应客户端的请求,但大概会返回差别等的数据


总结来说,CP 和 AP 是 CAP 定理中形貌的两种差别的设计选择,它们反映了分布式系统在差别场景下的权衡
选择 CP 还是 AP 取决于具体应用的需求,例如,金融系统通常需要强同等性,因此会选择CP,而社交媒体或某些类型的缓存系统大概会选择 AP 以提供更高的可用性
2. 什么环境下会出现Redis与数据库数据差别等

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

当客户端发起一个查询数据的请求时,会先检查 Redis 中检查有没有对应的数据,有的话直接返回,没有的话就会查询数据库,把从数据库中查询到的数据保存到 Redis 中后,再返回给客户端
   在将数据库中查询到的数据保存到 Redis 时,一样寻常会为数据设置一个过期时间,重要目的是为了避免一些冷数据不停占用 Redis 的空间
  如果只举行读操纵,是不会出现数据差别等的环境的,只有读操纵和写操纵同时举行,才会出现数据差别等的环境

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


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

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

  • 更新缓存还是删除缓存
  • 先操纵缓存还是先操纵数据库
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 实现数据同步的根本步骤和原理(人工智能给出的回答,仅供参考):

  • MySQL 主从复制原理

    • MySQL 支持主从复制功能,其中主库(Master)上的全部写操纵都会记录到二进制日志(Binary Log,简称 binlog)中
    • 从库(Slave)通过一个 I/O 线程连接到主库,请求主库的 binlog
    • 主库将 binlog 发送给从库,从库的 I/O 线程将 binlog 写入到当地的中继日志(Relay Log)
    • 从库的 SQL 线程读取中继日志,并执行日志中的写操纵,从而实现数据的复制

  • Canal 的工作流程

    • 模拟 Slave:Canal 模拟 MySQL 从库的举动,与主库创建连接,并请求主库的 binlog
    • 剖析 binlog:当主库有数据变动时,Canal 会接收到相应的 binlog 事件。Canal 使用本身的 binlog 剖析引擎来剖析这些事件,并将其转换为更容易明白的格式
    • 事件投递:剖析后的数据变动事件可以被投递到其他系统,如消息队列(例如 Kafka)或者直接写入到目标存储系统(例如 Elasticsearch)

  • 关键组件

    • Canal Server:运行 Canal 服务,负责从 MySQL 中读取 binlog,剖析并投递数据变动事件
    • Canal Client:负责从 Canal Server 获取数据变动事件,并将其应用到目标系统

  • 细节阐明

    • 位置记录:Canal 需要记录每次同步的位置,以便在服务重启后可以或许从前次停止的位置继续同步
    • 数据过滤:Canal 支持设置过滤规则,只同步特定数据库或表的数据
    • 数据格式转换:Canal 可以将剖析后的 binlog 事件转换为 JSON、XML 等格式

Canal 可以或许实现 MySQL 数据库与其他数据存储系统之间的实时数据同步,广泛应用于数据备份、数据迁移、数据集成和实时数据处理等场景
5.3 引入canal后的流程

简单地来说,当 MySQL 中出现了数据变动,canal 可以或许立马感知到,并关照 canal 的客户端
canal 的客户端有很多种,我们可以使用 SpringBoot 应用来充当 canal 的客户端,接收来自 canal 的关照,一旦数据库发生了数据修改,数据库的主节点会关照 canal,canal 再去关照 canal 的客户端
也就是说,更新数据库后的删除缓存操纵和删除重试中的删除缓存操纵都交由 canal 来完成

6. 总结


  • 如果是在并发量不高的的场景下,采用删除缓存→更新数据库→延迟删除缓存方案或更新数据库→删除缓存方案都是合理的
  • 如果是在高并发的场景下,无论是哪种方案,纵然对方案做了优化,都有大概出现数据差别等的环境,只不外是概率的大小问题
  • 保证 Redis 和数据库的数据同等性,一样寻常是保证数据的最终同等性,而不是数据的强同等性
  • 对于读多写少的数据,我们可以放在缓存中,减轻数据库的压力;对于读多写多的数据,放入缓存中弊大于利,因为放入缓存中的数据应该是对同等性要求没有那么高的数据
  • 如果数据要求强同等性,就需要借助锁来确保更新数据库操纵和删除缓存操纵的原子性,但引入锁会低沉系统的吞吐量,使用缓存本来就是为了提高系统的性能,引入锁反而是得不偿失
  • 同等性和可用性每每不可兼得,需要根据现实选择符合业务场景的方案

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莫张周刘王

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表