请选择 进入手机版 | 继续访问电脑版
尊悦模具刘沛昌 发表于 2020-12-31 20:23:11 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
​前言

 
在分布式情况中,数据副本 (Replica) 和复制 (Replication) 作为提升系统可用性和读写性能的有效手段被大量应用在各种分布式系统中,Redis 也不破例。
 
虽说现在根本不会直接使用主从复制来作为 Redis 的高可用方案,但是无论是哨兵照旧集群,都会使用到主从复制,因此,有须要先学习下主从复制的原理。
 
 
正文

 
主从复制实现原理

 

在当前最新的 Redis 6.0 中,主从复制的完整过程如下:
 
1、开启主从复制
通常有以下三种方式:
1)在 slave 直接执行命令:slaveof  
2)在 slave 设置文件中参加:slaveof  
3)使用启动命令:--slaveof  
 
注:在 Redis 5.0 之后,slaveof 相关命令和设置已经被替换成 replicaof,比方 replicaof  。为了兼容旧版本,通过设置的方式仍然支持 slaveof,但是通过命令的方式则不行了。
 
2、创建套接字(socket)毗连
slave 将根据指定的 IP 地点和端口,向 master 发起套接字(socket)毗连,master 在接受(accept) slave 的套接字毗连之后,为该套接字创建相应的客户端状态,此时毗连创建完成。
 
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   执行完整重同步
 
2)如果不是第一次执行复制,则向 master 发送 PSYNC replid offset,此中 replid 是 master 的复制 ID,而 offset 是 slave 当前的复制偏移量。master 根据 replid 和 offset 来判断应该执行哪种同步操纵。
 
如果是完整重同步,则返回 +FULLRESYNC  ;如果是部分重同步,则返回 +CONTINUE ,此时 slave 只需等候 master 将自己缺少的数据发送过来就可以。
 
9、命令流传
当完成了同步之后,就会进人命令流传阶段,这时 master 只要一直将自己执行的写命令发送给 slave ,而 slave 只要一直接收并执行 master 发来的写命令,就可以包管 master 和 slave 一直保持一致了。
 
在命令流传阶段, slave 默认会以每秒一次的频率,向 master 发送命令:REPLCONF ACK ,此中 reploff 是 slave 当前的复制偏移量。
 
发送REPLCONF ACK 命令对于主从服务器有三个作用:
 
1)检测 master 和 slave 的网络毗连状态。
 
2)汇报自己的复制偏移量,检测命令丢失,master 会对比复制偏移量,如果发现 slave 的复制偏移量小于自己,会向 slave 发送未同步的数据。
 
3)辅助实现 min-slaves 设置,用于防止 master 在不安全的情况下执行写命令。
 
比方以下设置表示:当延迟时间小于10秒的 slave 数量小于3个,则会拒绝执行写命令。而这边的延迟时间,就是以 slave 最近一次发送 ACK 时间和当前时间作对比。
  1. min-slaves-to-write 3min-slaves-max-lag 10
复制代码
 
以部分重同步为例,主从复制的焦点步调流程图如下:
 

 
相关源码在 replication.c,焦点方法是:replicationSetMaster、connectWithMaster、syncWithMaster
 
 
旧版同步:SYNC

 

Redis 2.8 之前的数据同步通过 SYNC 命令完成,完整流程如下:
 
1、slave 向 master 发送 SYNC 命令。
 
2、master 收到 SYNC 命令后执行 BGSAVE 命令,fork 子历程生成 RDB 文件,同时会使用一个缓冲区记录从现在开始执行的所有写命令。
 
Redis 在这边使用了 COW(copy-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 文件发送给 slave。slave 接收 RDB 文件,并载入到内存,将数据库状态更新至 master 执行 BGSAVE 时的数据库状态。
 
这边发送 RDB 文件的方式有两种:1)socket:master 将 RDB 文件流通过 socket 直接发送到 slave;2)disk:master 将 RDB 文件先恒久化到磁盘,再发送到 slave。
 
默认使用方式为 disk,可以通过以下设置来使用 socket 方式。
  1. repl-diskless-sync yes
复制代码
 
同时,相关的参数设置另有 diskless-sync-delay:该参数表示等候一定时长再开始复制,这样可以等候多个 slave 节点重新毗连上来。
 
socket(无磁盘)方式适合磁盘读写速度慢但网络带宽非常高的情况。
 
另外,这边主历程查抄子历程 BGSAVE 是否执行完毕是通逾期间事件定时查抄的。
 
4、master 将记录在缓冲区内里的所有写命令发送给 slave,slave 执行这些命令,将数据库状态更新至 master 当前所处的状态。
 
SYNC 存在的问题:slave 每次断线重连都需要使用完整重同步,效率低下。
 
 
新版同步:SYNC

 

为了管理 slave 每次断线重连都需要使用完整重同步,redis 在 2.8 版本引入了 PSYNC,PSYNC 包罗完整重同步和部分重同步。
 
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 命令发给 master,master 查抄自己的复制积存缓冲区,如果发现这部分未同步的命令还在自己的复制积存缓冲区中的话就可以使用这些生存的命令举行部分同步,反之如果断线太久这部分命令已经不在复制缓冲区中了,那没办法只能举行全量同步。
 
 
运行 ID(runid)
 
每个 Redis server 都会有自己的运行 ID,由 40 个随机的十六进制字符组成。当 slave 初次复制 master 时,master 会将自己的运行 ID 发给 slave 举行生存,这样 slave 重连时再将这个运行 ID 发送给重连上的 master ,master 会接受这个 ID 并与自身的运行 ID 比较进而判断是否是同一个 master。
 
 
引入这三个概念后,数据同步过程如下:
 
1)slave 通过 PSYNC runid offset 命令,将正在复制的 runid 和 offset 发送给 master。
 
2)master 判断 runid 和自己的 runid 相同,而且 offset 还在复制积存缓冲区,则举行部分重同步:通过复制积存缓冲区将 slave 缺失的命令发送给 slave,slave 执行命令,将数据库状态更新至 master 所处的状态。
 
3)否则,如果 master 判断 runid 不相同,或者 offset 已经不在复制积存缓冲区,则执行完整重同步。
 
PSYNC 的完整流程如下图:
 

 
 
PSYNC 存在的问题
 
通过上述流程,我们可以看出,PSYNC 执行部分重同步需要满足两个条件:1)master runid 不变;2)复制偏移量在 master 复制积存缓冲区中。一旦不满足这两个条件,则仍然需要举行完整重同步,比方以下场景。
 
1、slave 重启,缓存的 master runid 和 offset 都会丢失,slave 需举行完整重同步。
 
2、redis 发生故障切换,故障切换后 master runid 发生了变革,slave 需举行完整重同步。
 
slave 维护性重启、master 故障切换都是 redis 运维常见场景,因此,PSYNC 的这两个问题出现概率照旧非常高的。
 
相关源码在 replication.c,焦点方法是:syncCommand、readSyncBulkPayload、replicationFeedSlaves、backgroundSaveDoneHandler、slaveTryPartialResynchronization 等
 
 
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 服务器的唯一标识。
 
 
2、slave 也会开启复制积存缓冲区
 
slave 开启复制积存缓冲区,主要是用于故障切换后,当某个 slave 升级为 master,该 slave 仍然可以通过复制积存缓冲区继续支持部分重同步功能。
 
如果 slave 不开启复制积存缓冲区,当该 slave 升级为 master 后,复制积存缓冲区是空的,就没法支持部分重同步了。
 
 
接下来,让我们看看 Redis 是如何针对 PSYNC 的两个问题来举行优化。
 
 
优化场景1:slave 重启后导致完整同步
 
产生该问题的根本原因是 slave 重启后,复制 ID(运行 ID) 和 复制偏移量丢失了。管理办法实在很简单,就是在关闭服务器前将这两个变量存下来即可。
 
Redis 的做法如下:slave 在正常关闭前会调用 rdbSaveInfoAuxFields 函数把当前的复制 ID(replid) 和复制偏移量(master_repl_offset)作为辅助字段生存到 RDB 文件中,背面该 slave 重启的时候,就可以从 RDB 文件中读取复制 ID 和复制偏移量,然后使用这两个变量来举行部分重同步。
 
 
优化场景2:master 故障切换后导致完整重同步
 
产生该问题的根本原因是故障切换后出现了新的 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 生存了自己的复制 ID,replid2 生存了老 master 的复制ID。
 
这样,新 master 就可以通过 replid2 来判断 slave 是否之前跟自己从是从同一个 master 复制数据,如果是的话,则实验使用部分重同步。
 
PSYNC2 的完整流程如下,可以看出和 PSYNC 很雷同,主要区别在于紫色框部分。

 
相关源码根本同 PSYNC
 
 
主从复制的演变

 

从 Redis 2.* 到现在,开辟人员对主从复制流程举行逐步的优化,以下是演进过程:
 
1、2.8 版本之前 Redis 复制采用 SYNC 命令,无论是第一次复制照旧断线重连后的复制都采用完整重同步,资本高。
 
2、2.8 ~ 4.0 之间复制采用 PSYNC 命令,主要优化了 Redis 在断线重连时候可通过 runid 和 offset 信息使用部分重同步。
 
3、4.0 版本之后对 PSYNC 举行了优化,通常称为 PSYNC2,主要优化了 PSYNC 在 slave 重启和故障切换时的完整重同步问题。
 
 
最后

 
当你的才气还撑不起你的野心的时候,你就应该静下心来学习,愿你在我这里能有所收获。
 
如果你以为本文写的还不错,对你有帮助,请通过【点赞】让我知道,支持我写出更好的文章。
 
 
推荐阅读

口试必问的 Redis:RDB、AOF、肴杂恒久化
口试必问的 Spring,你懂了吗?
如何写一份让 HR 眼前一亮的简历(附模板)
字节、美团、快手焦点部分口试总结(真题剖析)
口试阿里,HashMap 这一篇就够了
口试必问的 MySQL,你懂了吗?
口试必问的线程池,你懂了吗?
4 年 Java 履历,阿里网易拼多多口试总结、心得体会
跳槽,如何选择一家公司
如何准备好一场大厂口试
MySQL 8.0 MVCC 焦点原明白析(焦点源码)
921天,咸鱼到阿里的修仙之路
复习2个月拿下美团offer,我都做了些啥
 
                                    
                                                步调员囧辉                                           
                                       
                CSDN认证博客专家                                        Spring                MySQL                JVM                            当你的才气还撑不起你的野心的时候,你就应该静下心来学习,愿你在我这里能有所收获。公众号:步调员囧辉,分享个人在Java学习路上的一些学习、思考、履历和总结。
来源:https://blog.csdn.net/v123411739/article/details/111903245
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

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

本版积分规则


专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )