面试必问的 Redis:主从复制

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 在分布式环境中,数据副本 (Replica) 和复制 (Replication) 作为提升系统可用性和读写性能的有效手段被大量应用在各种分布式系统中,Redis 也不例外。

前言

 


在分布式环境中,数据副本 (Replica) 和复制 (Replication) 作为提升系统可用性和读写性能的有效手段被大量应用在各种分布式系统中,Redis 也不例外。

 

虽说现在基本不会直接使用主从复制来作为 Redis 的高可用方案,但是无论是哨兵还是集群,都会使用到主从复制,因此,有必要先学习下主从复制的原理。

 

 

正文

 


主从复制实现原理

 

在当前最新的Redis 6.0 中,主从复制的完整过程如下:

 

1、开启主从复制

通常有以下三种方式:

1)在 slave 直接执行命令:slaveof <masterip> <masterport>

2)在 slave 配置文件中加入:slaveof <masterip> <masterport>

3)使用启动命令:--slaveof <masterip> <masterport>

 

注:在Redis 5.0 之后,slaveof 相关命令和配置已经被替换成 replicaof,例如 replicaof <masterip> <masterport>。为了兼容旧版本,通过配置的方式仍然支持 slaveof,但是通过命令的方式则不行了。

 

2、建立套接字(socket)连接

slave 将根据指定的 IP 地址和端口,向master 发起套接字(socket)连接,master 在接受(acceptslave 的套接字连接之后,为该套接字创建相应的客户端状态,此时连接建立完成。

 

3、发送PING命令

slave master 发送一个PING 命令,以检査套接字的读写状态是否正常、 master 能否正常处理命令请求。

 

如果slave 收到 "PONG" 回复,那么表示 master slave 之间的网络连接状态正常,并且 master 可以正常处理命令请求。

 

如果是其他回复或者没有回复,表示 master slave 之间的网络连接状态不佳或者 master 暂时没办法处理 slave 的命令请求,则slave 进入 error 流程:slave 断开当前的连接,之后再进行重试。

 

4、身份验证

如果master slave 都没有设置密码,则无需验证。

 

如果master slave 都设置了密码,并且密码相同,则验证成功。

 

否则,master slave 设置的密码不同、master slave 一个设置密码一个没设置密码都会返回错误。

 

所有错误情况都会令 slave 进入 error 流程:slave断开当前的连接,之后再进行重试。

 

5、发送端口信息

在身份验证通过后后, slave 将向 master 发送自己的监听端口号, master 收到后记录在 slave 所对应的客户端状态的 slave_listening_port 属性中。

 

6、发送IP地址

如果配置了slave_announce_ip,则 slave master 发送slave_announce_ip 配置的 IP 地址, master 收到后记录在slave 所对应的客户端状态的 slave_ip 属性。

 

该配置是用于解决服务器返回内网 IP 时,其他服务器无法访问的情况。可以通过该配置直接指定公网IP

 

7、发送CAPA

CAPA 全称是 capabilities,这边表示的是同步复制的能力。

 

slave 会在这一阶段发送 capa 告诉master 自己具备的(同步)复制能力, master 收到后记录在 slave 所对应的客户端状态的 slave_capa 属性。

 

CAPA 在最新的 Redis 6.0 版本中有两种值:eof psync2

 

eof 表示 slave 支持直接接收从socket 发送过来的 RDB 数据流,也就是无盘加载(diskless_load)。

 

psync2 表示 slave 支持Redis 4.0 引入的部分重同步 v2 版本,这个在下文会详细介绍。

 

8、数据同步

slave 将向 master 发送PSYNC 命令, master 收到该命令后判断是进行部分重同步还是完整重同步,然后根据策略进行数据的同步。

 

1)如果是 slave 第一次执行复制,则向 master 发送PSYNC ? -1 master 返回 +FULLRESYNC <replid> <offset> 执行完整重同步

 

2)如果不是第一次执行复制,则向 master 发送 PSYNC replid offset,其中replid master 的复制 ID,而offset slave 当前的复制偏移量。master 根据replid offset 来判断应该执行哪种同步操作。

 

如果是完整重同步,则返回 +FULLRESYNC <replid> <offset>;如果是部分重同步,则返回 +CONTINUE <replid>,此时slave 只需等待 master 将自己缺少的数据发送过来就可以。

 

9、命令传播

当完成了同步之后,就会进人命令传播阶段,这时 master 只要一直将自己执行的写命令发送给 slave ,而slave 只要一直接收并执行 master 发来的写命令,就可以保证 master slave 一直保持一致了。

 

在命令传播阶段, slave 默认会以每秒一次的频率,向 master 发送命令:REPLCONF ACK <reploff>,其中 reploff slave 当前的复制偏移量。

 

发送REPLCONF ACK 命令对于主从服务器有三个作用:

 

1)检测 master slave 的网络连接状态。

 

2)汇报自己的复制偏移量,检测命令丢失,master 会对比复制偏移量,如果发现 slave 的复制偏移量小于自己,会向 slave 发送未同步的数据。

 

3)辅助实现 min-slaves 配置,用于防止 master 在不安全的情况下执行写命令。

 

例如以下配置表示:当延迟时间小于10秒的 slave 数量小于3个,则会拒绝执行写命令。而这边的延迟时间,就是以 slave 最近一次发送 ACK 时间和当前时间作对比。

min-slaves-to-write 3min-slaves-max-lag 10

 

以部分重同步为例,主从复制的核心步骤流程图如下:

 image.png

image.png

 

相关源码在replication.c,核心方法是:replicationSetMasterconnectWithMastersyncWithMaster

 

 

旧版同步:SYNC

 

Redis 2.8 之前的数据同步通过 SYNC 命令完成,完整流程如下:

 

1slave master 发送SYNC 命令。

 

2master 收到 SYNC 命令后执行BGSAVE 命令,fork 子进程生成 RDB 文件,同时会使用一个缓冲区记录从现在开始执行的所有写命令。

 

Redis 在这边使用了 COWcopy-on-write)的特性,这边简单介绍一下。

 

fork 子进程时,一种比较愚蠢的做法是将父进程的整个地址空间拷贝一份给子进程,但这是非常耗时的,因此一般不会这么做。

 

另一种做法是,fork之后,父子进程共用父进程已有的地址空间,只有当父子进程要进行写操作时,才将要修改的内容复制一份,再进行写操作,这也是 copy-on-write 名字的由来。

 

回到本文,这边当主进程 fork 出子进程时,因为 COW 的关系,可以认为在fork 的这一刻,快照已经生成了,只是还没写到 RDB 文件。

 

那这边就有一个问题,RDB 文件是 fork 这一刻的数据,从 fork 这一刻到 master RDB 文件发送给 slave 之间,主进程还在继续执行写命令,这期间的写命令slave 怎么获得?

 

这就用到上面同时会使用一个缓冲区记录从现在开始执行的所有写命令,这个缓冲区会记录 fork 之后的所有写命令。

 

后面当master RDB 文件发送给 slave 后,master会继续将缓冲区中的写命令发送给 slave,也就是下面的第4步,从而保证slave 的数据是完整的。

 

3、当 BGSAVE 命令执行完毕,master 会将生成的RDB 文件发送给 slaveslave 接收RDB 文件,并载入到内存,将数据库状态更新至 master 执行 BGSAVE 时的数据库状态。

 

这边发送RDB 文件的方式有两种:1socketmaster RDB 文件流通过 socket 直接发送到 slave2diskmaster RDB 文件先持久化到磁盘,再发送到 slave

 

默认使用方式为disk,可以通过以下配置来使用 socket 方式。

repl-diskless-sync yes

同时,相关的参数配置还有 diskless-sync-delay:该参数表示等待一定时长再开始复制,这样可以等待多个slave 节点重新连接上来。

 

socket(无磁盘)方式适合磁盘读写速度慢但网络带宽非常高的环境。

 

另外,这边主进程检查子进程 BGSAVE 是否执行完毕是通过时间事件定时检查的。

 

4master 将记录在缓冲区里面的所有写命令发送给slaveslave 执行这些命令,将数据库状态更新至 master 当前所处的状态。

 

SYNC 存在的问题slave 每次断线重连都需要使用完整重同步,效率低下。

 

 

新版同步:SYNC

 

为了解决slave 每次断线重连都需要使用完整重同步,redis 2.8 版本引入了PSYNCPSYNC 包含完整重同步和部分重同步。

 

1、完整重同步:和 SYNC 命令基本一致。

 

2、部分重同步:slave 只需要接收和同步断线期间丢失的写命令即可,不需要进行完整重同步。

 

为了实现部分重同步,Redis 引入了复制偏移量、复制积压缓冲区和运行ID 三个概念。

 

复制偏移量(offset

 

执行主从复制的双方都会分别维护一个复制偏移量,master 每次向 slave 传播N 个字节,自己的复制偏移量就增加 N;同理 slave 接收 N 个字节,复制偏移量也增加N。通过对比主从之间的复制偏移量就可以知道主从间的同步状态。

 

 

复制积压缓冲区(replication backlog buffer

 

复制积压缓冲区是 master 维护的一个固定长度的 FIFO 队列,默认大小为 1MB

 

master 进行命令传播时,不仅将写命令发给 slave 还会同时写进复制积压缓冲区,因此 master 的复制积压缓冲区会保存一部分最近传播的写命令。

 

slave 重连上 master 时会将自己的复制偏移量通过 PSYNC 命令发给mastermaster 检查自己的复制积压缓冲区,如果发现这部分未同步的命令还在自己的复制积压缓冲区中的话就可以利用这些保存的命令进行部分同步,反之如果断线太久这部分命令已经不在复制缓冲区中了,那没办法只能进行全量同步。

 

 

运行 IDrunid

 

每个Redis server 都会有自己的运行 ID,由 40 个随机的十六进制字符组成。当 slave 初次复制master 时,master 会将自己的运行 ID 发给slave 进行保存,这样 slave 重连时再将这个运行 ID 发送给重连上的master master 会接受这个 ID 并与自身的运行ID 比较进而判断是否是同一个 master

 

 

引入这三个概念后,数据同步过程如下:

 

1slave 通过 PSYNC runid offset 命令,将正在复制的 runid offset 发送给master

 

2master 判断 runid 和自己的runid 相同,并且 offset 还在复制积压缓冲区,则进行部分重同步:通过复制积压缓冲区将slave 缺失的命令发送给 slaveslave 执行命令,将数据库状态更新至 master 所处的状态。

 

3)否则,如果 master 判断 runid 不相同,或者offset 已经不在复制积压缓冲区,则执行完整重同步。

 

PSYNC 的完整流程如下图:

 image.png

image.png

 

PSYNC 存在的问题

 

通过上述流程,我们可以看出,PSYNC 执行部分重同步需要满足两个条件:1master runid 不变;2)复制偏移量在 master 复制积压缓冲区中。一旦不满足这两个条件,则仍然需要进行完整重同步,例如以下场景。

 

1slave 重启,缓存的 master runid offset 都会丢失,slave 需进行完整重同步。

 

2redis 发生故障切换,故障切换后 master runid 发生了变化,slave需进行完整重同步。

 

slave 维护性重启、master 故障切换都是redis 运维常见场景,因此,PSYNC 的这两个问题出现概率还是非常高的。

 

相关源码在replication.c,核心方法是:syncCommandreadSyncBulkPayloadreplicationFeedSlavesbackgroundSaveDoneHandlerslaveTryPartialResynchronization

 

 

PSYNC2

 

为了解决PSYNC slave 重启和故障切换导致完整重同步的问题,Redis 4.0 版本中对 PSYNC 进行了优化,我们称为 PSYNC2

 

PSYNC2 进行了以下2个主要改动:

 

1、引入两组 replid offset 替换原来的 runid offset

 

第一组:replid master_repl_offset

对于master,表示为自己的复制 ID 和复制偏移量;

对于slave,表示为自己正在同步的 master 的复制 ID 和复制偏移量。

可以认为这一组的两个字段就是对应原来的 runid offset

 

第二组:replid2 second_repl_offset

对于master slave,都表示自己的上一个 master 的复制ID 和复制偏移量;主要用于故障切换时支持部分重同步。

 

值得注意的是,runid并不是在引入 replid 之后就不存在了。在 4.0 之前,redis使用 runid 来作为主从复制的标识,而在 4.0 后引入了replid 来作为主从复制的标识,但是,runid redis 中的功能不仅仅是作为主从复制的标识,runid 仍然有其他的功能,例如:用于作为 redis 服务器的唯一标识。

 

 

2slave 也会开启复制积压缓冲区

 

slave 开启复制积压缓冲区,主要是用于故障切换后,当某个slave 升级为 master,该 slave 仍然可以通过复制积压缓冲区继续支持部分重同步功能。

 

如果slave 不开启复制积压缓冲区,当该 slave 升级为 master 后,复制积压缓冲区是空的,就没法支持部分重同步了。

 

 

接下来,让我们看看 Redis 是如何针对 PSYNC 的两个问题来进行优化。

 

 

优化场景1slave 重启后导致完整同步

 

产生该问题的根本原因是 slave 重启后,复制 ID(运行ID复制偏移量丢失了。解决办法其实很简单,就是在关闭服务器前将这两个变量存下来即可。

 

Redis 的做法如下:slave 在正常关闭前会调用 rdbSaveInfoAuxFields 函数把当前的复制 IDreplid和复制偏移量(master_repl_offset)作为辅助字段保存到 RDB 文件中,后面该 slave 重启的时候,就可以从 RDB 文件中读取复制 ID 和复制偏移量,然后使用这两个变量来进行部分重同步。

 

 

优化场景2master 故障切换后导致完整重同步

 

产生该问题的根本原因是故障切换后出现了新的 master,而新 master 的复制ID(运行 ID)发生改变导致没法进行部分重同步。

 

在正常同步的情况下,新 master 的数据跟老 master 理论上是完全一致的,包括复制积压缓冲区的数据。

 

因此理论上slave 是可以进行部分重同步的,现在仅仅是因为复制 ID 变了而没法进行。所以,我们的目标就是想办法让新master 和其他 slave 可以串联起来。

 

master 和其他没有晋升的 slave 的共同点是故障切换前的 master 是相同的,因此很容易想到的做法是:利用故障切换前的 master 来串联新 master 和剩余slave

 

Redis 的做法如下:当节点从 slave 晋升为master 后,会将原来自己保存的第一组复制 ID 和复制偏移量(也就是老 master 的),移动到第二组复制 ID 和复制偏移量,然后将第一组复制 ID 重新生成一个新的,也就是属于自己的复制 ID

 

相当于,slave晋升为 master 后,replid 保存了自己的复制 IDreplid2 保存了老 master 的复制ID

 

这样,新master 就可以通过 replid2 来判断 slave 是否之前跟自己从是从同一个 master 复制数据,如果是的话,则尝试使用部分重同步。

 

PSYNC2 的完整流程如下,可以看出和 PSYNC 很类似,主要区别在于紫色框部分。

image.png

image.png


相关源码基本同PSYNC

 

 

主从复制的演变

 

Redis 2.* 到现在,开发人员对主从复制流程进行逐步的优化,以下是演进过程:

 

12.8 版本之前 Redis 复制采用SYNC 命令,无论是第一次复制还是断线重连后的复制都采用完整重同步,成本高。

 

22.8 ~ 4.0 之间复制采用 PSYNC 命令,主要优化了 Redis 在断线重连时候可通过 runid offset 信息使用部分重同步。

 

34.0 版本之后对 PSYNC 进行了优化,通常称为 PSYNC2,主要优化了 PSYNC slave 重启和故障切换时的完整重同步问题。

 

 

最后

 


当你的才华还撑不起你的野心的时候,你就应该静下心来学习,愿你在我这里能有所收获。

 

如果你觉得本文写的还不错,对你有帮助,请通过【点赞】让我知道,支持我写出更好的文章。

 

 

推荐阅读


面试必问的 RedisRDBAOF、混合持久化

面试必问的 Spring,你懂了吗?

如何写一份让 HR 眼前一亮的简历(附模板)

字节、美团、快手核心部门面试总结(真题解析)

面试阿里,HashMap 这一篇就够了

面试必问的 MySQL,你懂了吗?

面试必问的线程池,你懂了吗?

4 Java 经验,阿里网易拼多多面试总结、心得体会

跳槽,如何选择一家公司

如何准备好一场大厂面试

MySQL 8.0 MVCC 核心原理解析(核心源码)

921天,咸鱼到阿里的修仙之路

复习2个月拿下美团offer,我都做了些啥

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Redis Sentinel
【怒怼大厂面试官】听说你精通Redis?说说Redis哨兵
面试官:Redis哨兵知道吧?知道的,Sentinel哨兵本质是一个运行在特殊模式下的Redis服务器。面试官:嗯然后呢?它的主要作用是通过检测Redis主从服务器的下线状态,选举出新Redis主服务器,也就是故障转移,来保证Redis的高可用性。
78 4
【怒怼大厂面试官】听说你精通Redis?说说Redis哨兵
|
6天前
|
NoSQL MongoDB Redis
Python与NoSQL数据库(MongoDB、Redis等)面试问答
【4月更文挑战第16天】本文探讨了Python与NoSQL数据库(如MongoDB、Redis)在面试中的常见问题,包括连接与操作数据库、错误处理、高级特性和缓存策略。重点介绍了使用`pymongo`和`redis`库进行CRUD操作、异常捕获以及数据一致性管理。通过理解这些问题、易错点及避免策略,并结合代码示例,开发者能在面试中展现其技术实力和实践经验。
125 8
Python与NoSQL数据库(MongoDB、Redis等)面试问答
|
16天前
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
227 9
|
17天前
|
监控 NoSQL Redis
redis主从复制
redis主从复制
|
17天前
|
缓存 NoSQL 定位技术
深入探索Redis:面试中必须掌握的关键知识点
深入探索Redis:面试中必须掌握的关键知识点
|
23天前
|
NoSQL Java 测试技术
面试官:如何搭建Redis集群?
**Redis Cluster** 是从 Redis 3.0 开始引入的集群解决方案,它分散数据以减少对单个主节点的依赖,提升读写性能。16384 个槽位分配给节点,客户端通过槽位信息直接路由请求。集群是无代理、去中心化的,多数命令直接由节点处理,保持高性能。通过 `create-cluster` 工具快速搭建集群,但适用于测试环境。在生产环境,需手动配置文件,启动节点,然后使用 `redis-cli --cluster create` 分配槽位和从节点。集群动态添加删除节点、数据重新分片及故障转移涉及复杂操作,包括主从切换和槽位迁移。
31 0
面试官:如何搭建Redis集群?
|
1月前
|
存储 监控 NoSQL
Redis 架构深入:主从复制、哨兵到集群
大家好,我是小康,今天我们来聊下 Redis 的几种架构模式,包括主从复制、哨兵和集群模式。
Redis 架构深入:主从复制、哨兵到集群
|
1月前
|
运维 负载均衡 NoSQL
【大厂面试官】知道Redis集群和Redis主从有什么区别吗
集群节点之间的故障检测和Redis主从中的哨兵检测很类似,都是通过PING消息来检测的。。。面试官抓抓脑袋,继续看你的简历…得想想考点你不懂的😰。
67 1
|
1月前
|
SQL 网络协议 关系型数据库
【怒怼大厂面试官】听说你精通MySQL?来说说MySQL主从复制
面试官:MySQL主从复制了解吧?嗯嗯了解的。主要是利用了MySQL的Binary Log二进制文件。那我把二进制文件丢给从库,从库复制整个文件吗。噢噢不是的。
48 1
【怒怼大厂面试官】听说你精通MySQL?来说说MySQL主从复制
|
6月前
|
NoSQL Redis 数据库
Redis 主从复制的核心原理
Redis 主从复制的核心原理
32 0

热门文章

最新文章