1 缘起

从哪说起呢?这篇文章源于对Redis哨兵工作原理以及工作过程的模糊了解,
之前,仅仅知道Redis有哨兵模式,有PING和PONG过程,支持故障自动转移,
但是,对于细节并没有了解,更谈不到理解,于是,开始查看源码以及官方文档,
同时,查看前人的博客文章,按照自己的理解整理了这篇文章,
帮助读者轻松应对知识考核与知识交流。
前一篇文章:Redis进阶:哨兵工作原理(图+文,第一部分)https://blog.csdn.net/Xin_101/article/details/127134322
【关于排版】
特别说明:
为提高文章可读性,对方法间的调用的排版仅按照先后顺序进行,
没有严格对应到多级标题,而是采用模块化的方式排版,
每个模块中的方法调用是严格按照调用顺序拆分成多级标题,
模块间虽然又调用关系,但是,采用平行的同级标题,
不影响阅读与理解。

2 哨兵模式工作总流程

根据源码提取的Redis哨兵模式工作流程如下图所示,
由图可知,Redis哨兵模式的工作流程包括,
(1)初始化主节点信息
(2)主节点、从节点和哨兵节点递归执行逻辑,这里,我将其中的步骤独立出来分析;
(3)哨兵与主节点建立连接,订阅公共通道__sentinel__:hello,为后续向通道__sentinel__:hello发布消息做准备;
(4)定期发送PING、INFO命令,以及发送Hello消息,哨兵间同步配置信息;
(5)检测主节点主观下线,如果主观下线,则quorum+1,并会询问其他哨兵节点该主节点的运行状况;
(6)检测主节点客观下线,如果主节点客观下线,会先进入判断是否需要进行故障转移;
(7)故障转移询问,哨兵向其他哨兵节点询问当前主节点状态,如果需要故障转移,配置故障转移状态,为进入故障转移状态机做准备;
(8)进入故障转移状态机,进行故障转移一系列操作,如选举哨兵Leader,从节点升级为主节点等。
接下来每个步骤按照源码逐一解析。

3 初始化

哨兵模式初始化过程源码如下图所示,
其中,需要关注的是sentinel.master,创建相关的节点信息,
通过主节点填充相关的从节点以及哨兵等相关信息,为哨兵工作提供初始信息,
哨兵后续的处理参数,均是基于该参数,
这里不展开讲解是哪些数据,
在后续其他文章中讲解。

4 定时器:sentinelTimer

众所周知,Redis是单线程工作模式,但是,这并不影响其他的异步线程工作,
单线程只是针对客户端而言,其他的工作可以新开线程处理,
哨兵模式中哨兵节点与存储节点的消息传递以及故障转移则使用通过独立的线程执行定时执行,
即哨兵定时器(sentinelTimer),Redis哨兵定时器源码如下图所示,
由源码可知,通过定时器进入递归逻辑(sentinelHandleDictOfRedisInstances),
即哨兵模式的所有功能的执行入口,其中,入参记为初始化的主节点数据sentinel.master,
这个初始化数据贯穿Redis哨兵模式的全生命周期。

5 递归处理:sentinelHandleDictOfRedisInstances

递归逻辑源码如下图所示,有源码图可知,
将传入的实例信息列表(dict)遍历处理,
通过sentinelHandlRedisInstance方法进入具体的处理逻辑。

6 实例处理:sentinelHandleRedisInstance

集群所有实例的处理逻辑源码如下图所示,即sentinelHandlRedisInstance方法,
该方法即是真实的实例处理流程,流程图中的步骤均是从这个方法中提炼出来的,
由源码可知,实例处理过程包括:
(1)哨兵与节点建立里连接;
(2)哨兵定期向其他所有类型节点发布信息(通过命令,如PING和INFO);
(3)这里跳过tilt,在其他文章中讲解;
(4)检测主节点主观下线(S_DOWN);
(5)检测主节点客观下线(O_DOWN);
(6)判定是否进行故障转移,如果需要,则更新当前故障转移状态为:SENTINEL_FAILOVER_STATE_WAIT_START,为进入故障转移状态机做准备;
(7)进入故障转移状态机流程,本文后面会详细介绍状态机流程;
(8)询问其他哨兵主节点工作状态。

6.1 建立连接:sentinelReconnectInstance

哨兵与存储节点建立连接的源码如下图所示,
这里我截取了一部分,这部分核心是订阅公共通信通道:__sentinel__:hello
并回调接收消息的方法:sentinelReceiveHelloMessages,
获取节点信息,包括哨兵节点和存储节点(主节点以及从节点)。

6.2 周期性执行的命令:sentinelSendPeriodCommands

周期性执行有发送PING、INFO命令,发送Hello消息,
其中,PING用于判定主节点是否下线,
INFO用于获取节点相关信息,如IP、PORT等,
其中,PING是所有节点之间都会进行,哨兵不但要监测存储节点的状态,同时要监控哨兵的状态,
INFO用于获取集群中存储节点的信息,
哨兵间通过发送Hello信息交换/传递各自获取的存储节点信息,
这三个过程示意图如下图所示,由图可知,每个过程的执行周期是不同的,
INFO每10秒执行一次,PING每1秒执行一次,发送Hello信息每2秒执行一次。


上面的过程如何梳理的,源码在哪里?
源码如下图所示,由源码可知,定期执行命令以及逻辑在方法sentinelSendPeriodCommands中配置,
其中,INFO的时间周期为:SENTINEL_INFO_PERIOD,PING的时间周期为:SENTINEL_PING_PERIOD,
发送Hello信息的周期为:SENTINEL_PUBLISH_PERIOD。

6.3 主观下线:sentinelCheckSubjectlyDown

主节点主观下线的源码如下图所示,
由图可知,判定主观下线时,并不是直接进入下线判定逻辑,而是先经过两次是否重连的过程,
当不需要重连时(断开),才会进入主观下线判定的逻辑。

  • 第一次重连
    当命令连接(如PING)满足如下条件时,不进行重连,并直接断开连接。
    (1)命令连接时间大于最小重连时间;
    (2)PING不可用,即不为0;
    (3)PING间隔大于一半超时时间;
    (4)最近一次收到PONG的时间间隔超过一半超时时间。

  • 第二次重连
    当发布订阅(如INFO)连接满足如下条件时,不进行重连,并直接断开连接。
    (1)发布订阅连接时间大于最小重连时间;
    (2)最近一次发布订阅连接时间大于3倍的发布周期(SENTINEL_PUBLISH_PERIOD)。

主观下线的条件:
(1)主节点无响应;
(2)哨兵收到主节点响应的时间间隔大于超时时间+2倍的INFO周期。

6.4 客观下线:sentinelCheckObjectlyDown

客观下线源码如下图所示,由源码可知,
主节点客观下线没有重试连接的过程,因为已经经过了主观下线的重连过程,可以直接进入客观下线的逻辑。
客观下线的条件:
哨兵认定主节点主观下线,将此时的quorum+1,然后统计所有哨兵的主节点判定quorum,
如果quorum大于等于哨兵模式配置的quorum,
那么判定主节点客观下线,标识odown=1,触发故障转移。

6.5 判定是否需要直接开始故障转移:sentinelStartFailOverIfNeeded

Redis的故障转移设计,并不是判断主节点客观下线后直接就开始故障转移,
而是先进行故障转移判断,如果满足条件,开始故障转移处理逻辑,
这里的开始故障转移并不是直接进行故障转移,这只是准备阶段,
为进入故障转移状态机做准备,配置故障转移的状态为:SENTINEL_FAILOVER_STATE_WAIT_START,
故障转移状态机的第一个状态即为:SENTINEL_FAILOVER_STATE_WAIT_START。
判定是否进行故障转移的源码如下,由源码可知,
直接进行故障转移的条件如下,满足则直接进行故障转移:
(1)主节点确实标识为客观下线;
(2)没有正在执行的故障转移进程;
(3)没有正在尝试进行故障转移。


开始故障转移的源码如下图所示,由源码可知,
开始故障转移没有故障转移的实际处理逻辑,
仅为故障转移做了前期准备,如配置故障转移状态为SENTINEL_FAILOVER_STATE_WAIT_START,
为后续进入故障转移状态机的逻辑做准备。

6.6 询问其他哨兵关于主节点状态:sentinelAskMasterStateToOtherSentinels

当主节点被认定为主观下线后,哨兵会向其他哨兵节点询问当前主节点的运行状态,
询问其他哨兵节点源码如下图所示,由源码可知,
当哨兵A认定主节点为主观下线时,会向其他哨兵发送is-master-down-by-address请求,
获取对应的quorum,如果统计quorum达到配置的quorum数量则触发故障转移。
这里的细节有:
(1)如果从其他哨兵获取的主节点状态信息过于老旧(大于5倍的询问周期),则直接清除这个状态;
(2)主机点状态满足如在情况,哨兵会询问其他哨兵关于该主节点的运行状态信息:
(2.1)哨兵认定主节点主观下线;
(2.2)存在其他哨兵,并且与其他哨兵的连接通道正常通信;
(2.3)询问时间周期内未收到INFO信息。

6.7 故障转移

Redis哨兵模式故障转移设计非常有层次性,
好比网络的分层设计,各自为政,同时又相互关联,
故障转移过程描述:
哨兵判定主节点主观下线(S_DOWN)-》哨兵向其他哨兵确定主节点状态-》哨兵判定主节点客观下线(O_DOWN)-》哨兵选举Leader-》Leader哨兵选择从节点-》从节点升级为主节点-》主从节点相互同步信息。

6.7.1 故障转移的过程

哨兵模式故障转移工作流程如下图所示。
这里没有说明出现异常如何处理,如果失败, 则重试。

6.7.2 故障转移状态机:sentinelFailoverStateMachine

故障转移状态机才是真正执行故障转移的逻辑,
故障转移状态机源码如下图所示,由源码可知,
状态机通过故障转移状态failover_state进入不同的模块,执行对应的逻辑。
其中,由前文可知,第一个状态SENTINEL_FAILOVER_STATE_WAIT_START是从方法sentinelStartFailover中配置的,
其他的状态则实在各个模块中依次配置。

故障转移状态机中各个模块方法对应的功能如下图所示。
这里就不用文字进行描述了,看图即可,
后文会逐一讲解每个过程。

6.7.3 等待故障转移:sentielFailoverWaitStart

等待故障转移源码如下图所示,由源码可知,
等待启动故障转移时,
有个重要环节,即获取集群中的Leader哨兵,
即有哨兵Leader选举过程,这里先简单描述下过程,会在其他文章中介绍。
当哨兵判定主节点客观下线后,哨兵集群需要选择一个Leader执行故障转移,
此时,每个哨兵都有资格成为Leader,
Redis的哨兵Leader选择的算法Faft,基础思想是:先到先得,
在选举过程中,哨兵1向哨兵2发送成为Leader申请,如果哨兵2没有同意过其他哨兵,
则同意哨兵1为Leader,测试哨兵1选票+1,当哨兵1的选票vote满足:vote>=max(quorum, size(sentile)/2+1),
即选票大于等于法定哨兵个数quorum或者大于哨兵半数的最大数,则选择哨兵1为Leader,
由哨兵1完成故障转移,即选择从节点为主节点,并完成主从节点的信息同步。

6.7.4 选择从节点(主节点候选节点):sentinelFailoverSelectSlave

在哨兵中选择出Leader后,该Leader就肩负着故障转移的重任了,
哨兵Leader从余下的从节点中,使用快速排序,选择第一个节点为主节点候选,
即天选节点,
当然,Redis为从节点设计了优先级参数(replica-priority),保证从节节点升级为主节点的过程是可控的,
如何使用:
(1)replica-priority=0,该节点永远不会升级为主节点;
(2)replica-priority越小,优先级越高,即升级为主节点的可能性更大。
如两个可用的从节点,从节点S1和从节点S2,其中,S1的replica-priority=1,S2的replica-priority=10,
升级时,S1更有可能升级为主节点。
选出待升级的从节点后,更新故障转移状态为:SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE,
为进入状态机的下一个状态做准备。

6.7.5 为主节点选择从节点:sentinelFailoverSendSlaveOfNoOne

选出待升级的从节点后,需要为该继任的主节点招兵买马,
哨兵会将其他从节点信息传递给该新晋主节点,
源码如下图所示,由源码可知,
该过程,将从节点升级为主节点,
并将其他从节点升级为新的从节点,
同时,更新故障转移状态为:SENTINEL_FAILOVER_STATE_WAIT_PROMOTION,
其实,这是为监控从节点升级为主节点是否超时做准备,
进入监控状态机。

6.7.6 监控故障转移:sentinelFailoverWaitPromotion

Redis哨兵模式将从节点升级为主节点的过程分解为两个过程,
即升级过程,和监控过程,这个监控是监控升级超时的,
源码如下,由源码可知,当从节点升级为主节点的过程,出现超时,则终止故障转移。

6.7.7 更新从节点的主节点信息:sentinelFailoverReconfNextSlave

这是个什么过程?
当新主子登记后,由前文可知,哨兵已经将其他所有从节的信息上报给了这新晋主节点,
但是,这些没有成为主机点的从节点,只是单方面地发了投名状,
此时,从节点们并不知它们的新主子是谁?长什么样?
因此,需要有一个登基仪式,告知从节点们,我是你们新晋的主子,
而做这件事的方法源码如下,由源码可知,哨兵获取INFO回复的信息时,
更新节点相关信息,即把新晋的主节点信息(如IP和PORT等)发送给集群中的其他从节点,
并在最后一步检测故障转移是否结束。

上面的更新INFO过程是在sentinelRefreshInstanceInfo方法中进行的,
这个过程并没有串联在故障转移状态机中,
而是通过其他过程完成故障转移状态更新的,也是状态机的最后一个状态。
源码如下图所示,由源码可知,当满足故障转移的条件时,更新故障转移状态为:SENTINEL_FAILOVER_STATE_RECONF_SLAVES。

至此,完成Redis哨兵模式工作原理分析。

5 小结

Redis哨兵模式:
(1)通过异步定时执行相关任务,如哨兵与存储节点同步消息(PING、INFO)、哨兵间同步消息(Hello);
(2)节点间同步消息通过通道:__sentinel__:hello,因此有Publish和Subscribe,其中,哨兵向存储节点发送PING和INFO以及哨兵间发送Hello为PUB过程,在哨兵连接存储节点过程,进行订阅;
(3)PING的周期为:1秒,INFO命令的周期为:10秒,发送Hello的周期为:2秒;
(4)判定主观下线时,并不是直接进入下线判定逻辑,而是先经过两次是否重连的过程。主观下线的条件:a.主节点无响应;b.哨兵收到主节点响应的时间间隔大于超时时间+2倍的INFO周期;
(5)主节点客观下线没有重试连接的过程,因为已经经过了主观下线的重连过程,可以直接进入客观下线的逻辑。客观下线的条件:哨兵认定主节点主观下线,将此时的quorum+1,然后统计所有哨兵的主节点判定quorum,如果quorum大于等于哨兵模式配置的quorum,则判定主节点客观下线,标识odown=1,触发故障转移;
(6)故障转移过程:哨兵判定主节点主观下线(S_DOWN)-》哨兵向其他哨兵确定主节点状态-》哨兵判定主节点客观下线(O_DOWN)-》哨兵选举Leader-》Leader哨兵选择从节点-》从节点升级为主节点-》主从节点相互同步信息。