redis原理及进化之路

news2024/9/20 6:23:34

Redis 的主从复制经历了多次演进,本文将从最基本的原理和实现讲起,并层层递进,逐步呈现 Redis 主从复制的演进历史。大家将了解到 Redis 主从复制的原理,以及各个改进版本解决了什么问题,并最终看清 Redis 7.0 主从复制原理的全貌。

什么是主从复制?

在数据库语境下,复制(replication)就是将数据从一个数据库复制到另一个数据库中。主从复制,是将数据库分为主节点和从节点,主节点源源不断地将数据复制给从节点,保证主从节点中存有相同的数据。

有了主从复制,数据可以有多份副本,这带来了多种好处:

第一,提升数据库系统的请求处理能力。单个节点能够支撑的读流量有限,部署多个节点,并构成主从关系,用主从复制保持主从节点数据一致,如此主从节点可以一起提供服务。

第二,提升整个系统的可用性。因为从节点中有主节点数据的副本,当主节点宕机后,可以立刻提升其中一个从节点为主节点,继续提供服务。

图片

Redis 主从复制原理

实现主从复制,直观的思路是产生一份主节点数据的快照发送给从节点,并以此做为基准,随后将快照时刻之后的增量数据发送给从节点,如此就能保证主从数据的一致。总体来看,主从复制一般包含全量数据同步、增量同步两个阶段。

在 Redis 的主从复制实现中,包含两个类似阶段:全量数据同步和命令传播。

  • 全量数据同步:主节点产生一份全量数据的快照,即 RDB 文件,并将此快照发送给从节点。且从产生快照时刻起,记录新接收到的写命令。当快照发送完成后,将累积的写命令发送给从节点,从节点执行这些写命令。此时基准已经建立完成,主从节点间数据已经大体一致。
  • 命令传播:全量数据同步完成后,主节点将执行过的写命令源源不断地发送给从节点,从节点执行这些命令,保证主从节点中数据有相同的变更,如此保证主从数据持续一致。

下图中给出了 Redis 主从复制的整个过程:

图片

  1. 主从关系建立后,从节点向主节点发送一个 SYNC 命令请求进行主从同步。
  2. 主节点收到 SYNC 命令后,执行 fork 创建一个子进程,子进程中将所有的数据按特定编码存储到 RDB(Redis Database) 文件中,这就产生了数据库的快照。
  3. 主节点将此快照发送给从节点,从节点接收并载入快照。
  4. 主节点接着将生成快照、发送快照期间积压的写命令发送给从节点,从节点接收这些命令并执行,命令执行后,从节点中的数据也就有了同样的变更。
  5. 此后,主节点源源不断地新执行的写命令同步到从节点,从节点执行传播来的命令。如此,主从数据保持一致。需要说明的是,命令传播存在时延的,所以任意时刻,不能保证主从节点间数据完全一致。

以上就是 Redis 主从复制的基本原理,很简单很容易理解,Redis 最初就采用这种方案,但这种方案存在一些问题:

fork 耗时过长,阻塞主进程

执行fork 时,需要拷贝大量的内存页表,这是一个耗时较多的操作,尤其当内存使用量较大的时候。组内同学曾做过测试,内存占用 10GB 时,fork 需要消耗 100 多毫秒。fork 的时候主进程阻塞 100 多毫秒,这对 Redis 而言,实在太长了。另外fork 之后,如果主库中有不少的写入,那么由于写时复制机制,会额外消耗不少的内存,还会增大响应时间。

主从间网络闪断会触发全量同步

假如主从之间的网络出现了故障,连接意外断开,主节点无法继续传播命令至该从节点。之后网络恢复,从节点重新连接上主节点后,主节点不能再继续传播新接收到的命令了,因为从节点已经漏掉了一些命令。此时,从节点需要从头再来,再次执行全部的同步过程,而这要付出很高的代价。

网络闪断是常发生的事情,闪断期间主节点中可能只写入了比较少的数据,但就因为这很少的一部分数据,需要让从节点进行一次代价高昂的全量同步。这种做法是非常低效的,该如何解决这问题呢?下一节 Redis 部分重同步给你答案。

Redis 部分重同步

网络短暂断开后,从节点需要重新同步,这很浪费资源,很不环保。从节点为什么需要重新同步呢?因为主从断开期间有部分命令没有同步到从节点上去。如果忽略这些命令继续传播后续的命令,则会导致数据的错乱,因为丢失掉的命令是不能忽略的。为什么不将那些命令保存下来呢?这样当从节点重新连接后,就可以将断连期间的命令补充给它了,这样就不需要重新全量同步了。

Redis 2.8 版本后,引入了部分同步。它在主节点中维护了一个复制积压缓冲区,命令一方面会传播到从节点,另外还会记录在这个缓冲区中。保存所有的命令是不必要的,Redis 中使用了一个环形的缓冲区,这样就可以只保留最近的一些命令了。

图片

命令是保存下来了,但从节点重新连接后,主节点该从什么地方开始给从节点发送命令呢?如果能给所有命令编一个号,则从节点只需要告诉主节点自己最后收到的命令的编号,主节点就知道该从什么位置发送命令了。Redis 的实现中是对字节进行编号,这个编号在 Redis 的语境中叫做复制偏移量。

有了部分同步后,主从复制的流程变成了下面这样:

图片

主从复制的时候不再使用SYNC命令,而是使用PSYNC,意思的Partial SYNC,部分同步。PSYNC的语法如下:

PSYNC <master id> <replication offset>

命令中的两个参数,一个是主节点的编号,一个是复制偏移量。每个Redis节点都有一个 40 字节的编号,PSYNC 命令中携带的编号是期望进行同步的主节点的编号。复制偏移量则表示当前从节点想要从什么地方开始部分同步。

如果是第一次进行主从复制,自然是不知道主节点的编号,复制偏移量也无意义,此时使用 PSYNC ? -1 来进行全量同步。另外,如果从节点指定的复制偏移量不在主节点的复制积压缓冲区的范围内,部分同步会失败,会转向全量同步。

有了部分同步,网络闪断后就可以避免全量同步了。但是因为主节点只能保留最近的部分命令,保存多少取决于复制积压缓冲区的大小。如果从节点断开时间过长,或者断开期间主节点新执行的写命令足够多,漏掉的命令就无法全部保存到复制积压缓冲区中了。加大复制积压缓冲区可以尽可能多地避免全量同步,但这同时会造成额外的内存消耗。

部分同步消耗了部分内存来保存最近执行的写命令,避免闪断后的全同步,这是很直观、很容易想象的解决方案。这种方案很好,是否还存在其他问题呢?考虑以下问题:

假如从节点重启了怎么办?

部分同步依赖主节点的编号和复制偏移量,从节点在初次同步的时候会获取到主节点的编号,并在之后的同步中不断调整复制偏移量,这些信息都存储在内存中。当从节点意外重启后,尽管本地存有 RDB 或 AOF 文件,还是需要进行一次全量同步。但实际上完全可以载入本地数据,并执行部分同步即可。

假如主从切换了怎么办?

假如主节点意外宕机,外围监控组件执行了主从切换。此时其他从节点对应的主节点就变化了,从节点中记录的主节点编号就匹配不上新的主节点了,此时会进行一次全量同步。但实际上所有的从节点在主从切换之前同步进度应该是差不多的,而且新提升的从节点包含的数据应该最全,切主后所有从节点都执行一次全量同步,这实在不合理。

图片

以上问题如何解决,请继续往后看。

同源增量同步

从节点重启后丢失了原主节点编号和复制偏移量,这导致重启后需要全量同步,这很好办,把这些信息存下来就可以了。

主从切换后,主节点信息变化了,导致从节点需要全量同步,这也容易解决,只需能确认新主节点上的数据是从原主节点复制来的,那就可以继续从新的主节点上进行复制。

Redis 4.0 以后,对 PSYNC 进行了改进,提出了同源增量复制的解决方案,该方案解决了前面提到的两个问题。

从节点重启后,需要跟主节点全量同步,这本质上是因为从节点丢失了主节点的编号信息,在 Redis 4.0 后,主节点的编号信息被写入到 RDB 中持久化保存。

切主后,从节点需要和新主节点全量同步,本质原因是新的主节点不认原主节点的编号。从节点发送 PSYNC <原主节点编号> <复制偏移量> 给新的主节点,如果新的主节点能够认识 <原主节点编号>,并明白自己的数据就是从该节点复制来的。那么新的主节点就应该清楚它和该从节点师出同门,应该接受部分同步。如何才能识别呢,只需要让从节点在切换为主节点时,将自己之前的主节点的编号记录下来即可。

Redis 4.0 以后,主从切换后,新的主节点会将先前的主节点记录下来,观察 info replication 的结果,可以可以看到 master_replid 和 master_replid2 两个编号,前者是当前主节点的编号,后者为先前主节点的编号:

127.0.0.1:6379> slaveof no one
OK
127.0.0.1:6379> info replication
#Replication
role:master
...
master_replid:b34aff08d983991b3feb4567a2ac0308984a892a
master_replid2:a3f2428d31e096a99d87affa6cc787cceb6128a2
master_repl_offset:38599
second_repl_offset:38600
...
repl_backlog_histlen:5180

Redis 中目前值保留了两个主节点编号,但完全可以实现一个链表,将过往的主节点的编号信息都记录下来,这样就可以追溯的更远了。这样以来,如果一个从节点断开后,执行了多次主从切换,该从节重新连接后,依然可以识别出它们的数据是同源的。但 Redis 没有这么做,这是因为没有必要,因为就算数据是同源的,但复制积压缓冲区中保存的数据是有限的,多次主从切换后,复制积压缓冲区中保存的命令已经无法满足部分同步了。

有了同源增量复制后,主节点切换后,其他从节点可以基于新的主节点继续增量同步。

此时,主从复制看起来已经不存在太大的问题了。但做 Redis 的那帮家伙,总挖空心思想着能不能再做些优化。下面我将描述 Redis 主从复制的一些优化策略。

无盘全量同步和无盘加载

Redis 执行全量复制,需要生成当前数据库的一份快照,具体做法是执行 fork 创建子进程,子进程遍历所有数据并编码后写入 RDB 文件中。RDB 生成后,在主进程中,会读取此文件并发送给从节点。

读写磁盘上的 RDB 文件是比较耗资源的,在主进程中执行势必会导致 Redis 的响应时间变长。因此一个优化方案是 dump 后直接将数据直接发送数据给从节点,不需要将数据先写入到 RDB 。Redis 6.0 中实现了这种无盘全量同步和无盘加载的策略。

采用无盘全量同步,避免了对磁盘的操作,但也有缺点。一般情况下,在子进程中直接使用网络发送数据,这比在子进程中生成 RDB 要慢,这意味着子进程需要存活的时间相对较长。子进程存在的时间越长,写时复制造成的影响就越大,进而导致消耗的内存会更多。

在全量复制时候,从节点一般是先接收 RDB 将其存在本地,接收完成后再载入 RDB。同样地,从节点也可以直接载入主节点发来的数据,避免将其存入本地的 RDB 文件中,而后再从磁盘加载。

共享主从复制缓冲区

在主节点的视角中,从节点就是一个客户端,从节点发送了 PSYNC 命令后,主节点就要与它们完成全量同步,并不断地把写命令同步给从节点。Redis 的每个客户端连接上存在一个发送缓冲区。

主节点执行了写命令后,就会将命令内容写入到各个连接的发送缓冲区中。发送缓冲区存储的是待传播的命令,这意味着多个发送缓冲区中的内容其实是相同的。而且,这些命令还在复制积压缓冲区中存了一份呢。这就造成了大量的内存浪费,尤其是存在很多从节点的时候。

图片

在 Redis 7.0 中,我们团队的同学提出并实现了共享主从复制缓冲区的方案解决了这个问题。该方案让发送缓冲区与复制积压缓冲区共享,避免了数据的重复,可有效节省内存。

总结

本文回顾并总结了 Redis 主从复制的演化过程,并解释了各次演化所解决的问题。最后,描述了对 Redis 主从复制进行优化的一些策略。

下面是对全文的总结:

  1. 宏观来看 Redis 的主从复制分为全量同步和命令传播两个阶段。主节点先发送快照给从节点,然后源源不断地将命令传播给从节点,以此保证主从数据的一致。
  2. Redis 2.8 之前的主从复制存在闪断后需要重新全量同步的问题,Redis 2.8 引入了复制积压缓冲区解决了这一问题。
  3. 在 Redis 4.0 中,同源增量复制的策略被提出,解决了主从切换后从节点需要全量同步的问题。至此,Redis 的主从复制整体上已经比较完善了。
  4. Redis 6.0 中,为进一步优化主从复制的性能,无盘同步和加载被提出,避免全量同步时读写磁盘,提高主从同步的速度。
  5. 在 Redis 7.0 rc1 中,采用了共享主从复制缓冲区的策略,降低了主从复制带来的内存开销。

希望本文能帮助大家回顾 Redis 主从复制的原理,并对其建立更加深刻的印象。

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

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

相关文章

vue+uniapp疫苗预约接种系统 微信小程序

统计分析&#xff1a;查看用户&#xff0c;疫苗&#xff0c;订单数量&#xff1b;统计近7日&#xff0c;30日订单趋势 用户管理&#xff1a;查看注册用户信息&#xff0c;及删除&#xff08;数据库mysql) 疫苗管理&#xff1a;疫苗增删改查以及上下架 接种点管理&#xff1a;接…

vue3.0 详细说明+案例 !!!

提示&#xff1a;vue3.0 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;Vuee.js的最新版本。它提供了一系列全新的特性&#xff0c;包括更快的渲染速度、更好的Tree Shaking支持以及更好的TypeScript支持。 最重要的特性…

centos7重启后/etc/rc.local中的脚本没有执行

前阵子自己安装了WMware16、centos7&#xff0c;配置好jdk、mysql、nginx、redis并设置好开机自动启动后&#xff0c;打算将服务也做成自启动&#xff0c;因为之前做过本以为会很顺利&#xff0c;结果整了快两小时&#xff0c;觉得有必要记录下。 之前自己记录的博客&#xff…

Faster RCNN系列5——RoI Pooling与全连接层

Faster RCNN系列&#xff1a; Faster RCNN系列1——Anchor生成过程 Faster RCNN系列2——RPN的真值与预测值概述 Faster RCNN系列3——RPN的真值详解与损失值计算 Faster RCNN系列4——生成Proposal与RoI Faster RCNN系列5——RoI Pooling与全连接层 在RPN网络中&#xff0c;已…

融云出海赋能会干货回顾(二)| 地区、赛道选择和避坑攻略

“出海是这个时代给我们的机遇。”这是很多互联网出海人的心声。关注【融云全球互联网通信云】了解更多 走过跌宕起伏的 15 年出海历程&#xff0c;中国出海人现在面对与此前截然不同的市场环境&#xff0c;很多地区蓝海不再&#xff0c;也有不少赛道变得拥挤。 一体两面&…

交友项目【动态点赞动态喜欢】

目录 1&#xff1a;点赞 1.1&#xff1a;动态点赞 1.1.1&#xff1a;分析&实现 1.2&#xff1a;取消点赞 1.2.1&#xff1a;分析&实现 2&#xff1a;喜欢 2.1&#xff1a;动态喜欢 2.1.1&#xff1a;分析&实现 2.2&#xff1a;取消喜欢 2.2.2&#xff1a…

学成在线笔记+踩坑(5)——【媒资模块】上传视频,断点续传

目录 5 上传视频 5.1 媒资管理页面上传视频流程预览 5.2 断点续传技术 5.2.1 什么是断点续传 5.2.2 测试分块与合并&#xff0c;RandomAccessFile随机流 5.2.3 视频上传流程 5.2.4 测试minio合并文件 5.3 接口定义&#xff0c;检查文件/分块、上传分块、合并分块 5.4…

4年外包终上岸,我只能说这类公司能不去就不去

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是4年。现在终于跳槽到了互联网公司了&#xff0c;我想说的是&#xff0c;但凡有点机会&#xff0c;千万…

类图(类之间的关系)

一.概述 类图(Class diagram)是显示了模型的静态结构&#xff0c;特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。在软件工程中&#xff0c;类图是一种静态的结构图&#xff0c;描述了系统的类的集合…

基于Powell共轭方向法的UWB室内定位构型优化算法

基于Powell共轭方向法的UWB室内定位构型优化算法 阚昊宇 摘要&#xff1a; UWB室内定位系统的服务性能及定位精度很大程度上受UWB基站构型影响&#xff0c;而GDOP是衡量系统定位服务性能的重要指标。目前关于UWB室内定位基站构型的讨论主要集中于最小化限定基站数目下GDOP在自…

【系统集成项目管理工程师】项目资源管理

&#x1f4a5;十大知识领域&#xff1a;项目资源管理 项目资源管理包括以下 4 个过程: 编制项目人力资源计划组建项目团队建设项目团队管理项目团队 一、编制项目人力资源计划 确定与识别项目中的角色、所需技能、分配项目职责和汇报关系&#xff0c;并记录下来形成书面文件&am…

CDN如何阻止网络攻击

随着网络技术的发展&#xff0c;网络攻击事件也越来越多&#xff0c;对企业和个人的安全和稳定造成严重威胁。为此&#xff0c;高防CDN应运而生&#xff0c;成为广大用户保障网络安全的重要工具。什么是高防CDN?高防CDN的特点有哪些?高防CDN如何阻止网络攻击?接下来让我们一…

【云原生|Docker】14-Dokcer Harbor高可用部署

【云原生Docker】14-Dokcer Harbor高可用部署 文章目录 【云原生Docker】14-Dokcer Harbor高可用部署前言Harbor高可用方案单主复制双主复制多Harbor共享后端存储 Harbor高可用部署方案说明环境说明部署步骤安装nfs安装redis和PostgreSQL安装harbor配置nginx访问测试 总结 前言…

netty源码学习之-HashedWheelTimer

netty源码学习之-HashedWheelTimer 概述使用相关概念解析时间轮 运行时序图 源码workerHashedWheelTimeoutHashedWheelBucket 概述 该部分源码是netty的时间轮&#xff0c;netty的时间轮是单轮&#xff0c;其他时间轮是多轮设计&#xff0c;今天先了解下netty的时间轮设计 使用…

hot100:数组——49、53、55

49. 字母异位词分组 用hashmap存储&#xff0c;其中每个key&#xff0c;用这组异位词的排序后的字符串&#xff1b;value是这组异位词。比如“tea”和“ate”是一组异位词&#xff0c;他们的排序结果都是“aet”。 public List<List<String>> groupAnagrams(Stri…

交流电中的无功功率和有功功率,减少无功功率

有功&#xff0c;无功功率 从字面上理解就是做功和不做功的功率。不做功的是因为负载电路中有电感和电容的存在。 电容和电感的电压电流关系 设加在两端的电压都是 U U m a x s i n w t UU_{max}sinwt UUmax​sinwt 电容和电感两端电压电流的关系 电容两端的电压电流关系…

Javaee Spring基于XML的AOP开发

快速入门 1. 导入 AOP 相关坐标 2. 创建目标接口和目标类&#xff08;内部有切点&#xff09; 3. 创建切面类&#xff08;内部有增强方法&#xff09; 4. 将目标类和切面类的对象创建权交给 spring 5. 在 applicationContext.xml 中配置织入关系 6. 测试代码 项目…

【数据库】MySQL数据约束和表关系详解

目录 1.数据库约束 1.1约束类型 1.2NULL约束 1.3UNIQUE&#xff1a;唯一约束 1.4DEFAULT&#xff1a;默认值约束 1.5PRIMARY&#xff1a;主键约束 1.6FOREIGH KEY:外键约束 2.表的关系 2.1一对一 2.2一对多 2.3多对多 1.数据库约束 数据库中的数据保存在数据表中&am…

数据库基础篇 《9. 子查询》

目录 1. 需求分析与问题解决 1.1 实际问题 1.2 子查询的基本使用 ​编辑1.3 子查询的分类 分类方式1&#xff1a;我们按内查询的结果返回一条还是多条记录&#xff0c;将子查询分为 单行子查询 、 多行子查询 。 分类方式2&#xff1a; 我们按内查询是否被执行多次&#x…

4WE10D50型力士乐液压阀规格

安装位置 可选择&#xff0c;方向阀最好水平安装&#xff01;对于阀品种&#xff0c;如 &#xff0d; 不带阀芯对中弹簧 &#xff0d; 或带下垂电磁铁 其他安装位置能够导致功能失常或违反有关的技朮规定。 带泄油口的压力开关的安装位置必须选择&#xff0c;使它的泄油口…