摘要:本文通过对Redis Sentinel源码的了解,具体阐明Sentinel的代码实现形式。

Redis Sentinel 是Redis提供的高可用模型解决方案。Sentinel能够主动监测一个或多个Redis主备实例,并在主实例宕机的状况下主动履行主备倒换。本文通过对Redis Sentinel源码的了解,具体阐明Sentinel的代码实现形式。

Sentinel应用Redis内核雷同的事件驱动代码框架, 但Sentinel有本人独特的初始化步骤。在这篇文章里,会从Sentinel的初始化、Sentinel主工夫事件函数、Sentinel 网络连接和Tilt模式三局部进行解说。

Sentinel初始化

咱们能够通过redis-sentinel <path-to-configfile> 或者 redis-server <path-to-configfile> --sentinel 这两种形式启动并运行Sentinel实例,这两种形式是等价的。在Redis server.c 的main函数中,咱们会看到Redis如何判断用户指定以Sentinel形式运行的逻辑:

其中checkForSentinelMode函数会监测以下两种条件:

  1. 程序应用redis-sentinel可执行文件执行。
  2. 程序参数列表中有--sentinel 标记。

以上任何一种条件成立则Redis会应用Sentinel的形式运行。

在Redis 判断是否以Sentinel的形式运行当前,咱们会看到如下代码段:

在initSentinelConfig函数中,会应用Sentinel特定的端口(默认为26379)来代替Redis的默认端口(6379)。另外,在Sentinel模式下,须要禁用服务器运行保护模式。

与此同时,initSentinel函数会做如下操作:

1.应用Sentinel自带的命令表去代替Redis服务器原生的命令. Sentinel 反对的命令表如下:

2.初始化Sentinel主状态构造,Sentinel主状态的定义及正文如下。

其中masters字典指针中的每个值都对应着一个Sentinel检测的主实例。

在读取配置信息后,Redis服务器主函数会调用sentinelIsRunning函数, 做以下几个工作:

  1. 查看配置文件是否被设置,并且检查程序对配置文件是否有写权限,因为如果Sentinel状态扭转的话,会一直将本人以后状态记录在配置文件中。
  2. 如果在配置文件中指定运行ID,Sentinel 会应用这个ID作为运行ID,相同地,如果没有指定运行ID,Sentinel会生成一个ID用来作为Sentinel的运行ID。
  3. 对所有的Sentinel监测实例产生初始监测事件。

Sentinel的主工夫事件函数

Sentinel 应用和Redis服务器雷同的事件处理机制:分为文件事件和工夫事件。文件事件解决机制应用I/O 多路复用来解决服务器端的网络I/O 申请,例如客户端连贯,读写等操作。工夫解决机制则在主循环中周期性调用工夫函数来解决定时操作,例如服务器端的保护,定时更新,删除等操作。Redis服务器主工夫函数是在server.c中定义的serverCron函数,在默认状况下,serverCron会每100ms被调用一次。在这个函数中,咱们看到如下代码:

其中当服务器以sentinel模式运行的时候,serverCron会调用sentinelTimer函数,来运行Sentinel中的主逻辑,sentinelTimer函数在sentinel.c中的定义如下:

Sentinel Timer函数会做如下几个操作:

  1. 查看Sentinel以后是否在Tilt 模式(Tilt模式将会在稍后章节介绍)。
  2. 查看Sentinel与其监控主备实例,以及其余Sentinel实例的连贯,更新以后状态,并在主实例下线的时候主动做主备倒换操作。
  3. 查看回调脚本状态,并做相应操作。
  4. 更新服务器频率(调用serverCron函数的频率),加上一个随机因子,作用是避免监控雷同主节点的Sentinel在选举Leader的时候工夫抵触,导致选举无奈产生相对多的票数。

其中SentinelHandleDictOfRedisInstances函数的定义如下: 

SentinelHandleDictOfRedisInstances函数次要做的工作是:

1.调用sentinelHandleDictOfRedisInstance函数解决Sentinel与其它特定实例连贯,状态更 新,以及主备倒换工作。

  1. 如果以后解决实例为主实例,递归调用SentinelHandleDictOfRedisInstances函数解决其上司的从实例以及其余监控这个主实例的Sentinel。
  2. 在主备倒换胜利的状况下,更新主实例为降级为主实例的从实例。

其中在sentinelHandleRedisInstance的定义如下:

这个函数会做以下两局部操作:

1.查看Sentinel和其余实例(主备实例以及其余Sentinel)的连贯,如果连贯没有设置或曾经断开连接,Sentinel会重试绝对应的连贯,并定时发送响应命令。 须要留神的是:Sentinel和每个主备实例都有两个连贯,命令连贯和公布订阅连贯。然而与其余监听雷同主备实例的Sentinel只保留命令连贯,这部分细节会在网络章节独自介绍。

2.第二局部操作次要做的是监测主备及其他Sentinel实例,并监测其是否在主观下线状态,对于主实例来说,还要检测是否在主观下线状态,并进行相应的主备倒换操作。

须要留神的是第二局部操作如果Sentinel在Tilt模式下是疏忽的,上面咱们来看一下这个函数第二局部的的具体实现细节。

sentinelCheckSubjectivelyDown 函数会监测特定的Redis实例(主备实例以及其余Sentinel)是否处于主观下线状态,这部分函数代码如下:

主观下线状态意味着特定的Redis实例满足以下条件之一:

  1. 在实例配置的down_after_milliseconds工夫内没有收到Ping的回复。
  2. Sentinel认为实例是主实例,但收到实例为从实例的回复,并且上次实例角色回复工夫大于在实例配置的down_after_millisecon工夫加上2倍INFO命令距离。

如果任何一个条件满足,Sentinel会关上实例的S_DOWN标记并认为实例进入主观下线状态。

主观下线状态意味着Sentinel主观认为实例下线,但此时Sentinel并没有询问其余监控此实例的其余Sentinel此实例的在线状态。

sentinelCheckObjectivelyDown 函数会查看实例是否为主观下线状态,这个操作仅仅对主实例进行。sentinelCheckObjectivelyDown函数定义如下:

这个函数次要进行的操作是循环查看监控此主实例的其余Sentinel SRI_MASTER_DOWN 标记是否关上,如果关上则意味着其余特定的Sentinel认为主实例处于下线状态,并统计认为主实例处于下线状态的票数,如果票数大于等于主实例配置的quorum值,则Sentinel会把主实例的SRI_O_DOWN标记关上,并认为主实例处于主观下线状态。

sentinelStartFailoverIfNeeded函数首先会查看实例是否处于主观下线状态(SRI_O_DOWN标记是否关上),并且在2倍主实例配置的主备倒换超时工夫内没有进行主备倒换工作,Sentinel会关上SRI_FAILOVER_IN_PROGRESS标记并设置倒换状态为SENTINEL_FAILOVER_STATE_WAIT_START。并开始进行主备倒换工作。主备倒换的细节将在主备倒换的章节里介绍。

Sentinel的网络连接

上文提到每个Sentinel实例会保护与所监测的主从实例的两个连贯,别离是命令连贯(Command Connection)和公布订阅连贯(Pub/Sub Connection)。然而须要留神的是,Sentinel和其余Sentinel之间只有一个命令连贯。上面将别离介绍命令连贯和公布订阅连贯的作用。

命令连贯

Sentinel保护命令连贯是为了与其余主从实例以及Sentinel实例通过发送接管命令的形式进行通信,例如:

  1. Sentinel会默认以每1s距离发送PING 命令给其余实例以主观判断其余实例是否下线。
  2. Sentinel会通过Sentinel和主实例之间的命令连贯每隔10s发送INFO命令给主从实例以失去主实例和从实例的最新信息。
  3. 在主实例下线的状况下,Sentinel会通过Sentinel和从实例的命令连贯发送SLAVEOF NO ONE命令给选定的从实例从而使从实例晋升为新的主节点。
  4. Sentinel会默认每隔1s发送is-master-down-by-addr命令以询问其余Sentinel节点对于监控的主节点是否下线。

在sentinel.c中的sentinelReconnectInstance函数中,命令连贯的初始化如下:

公布订阅连贯

Sentinel保护和其余主从节点的公布订阅连贯作用是为了获知其余监控雷同主从实例的Sentinel实例的存在,并且从其余Sentinel实例中更新对所监控的主从实例以及发送的Sentinel实例的认知。例如在主备倒换实现后,其余Sentinel通过读取领头的Sentinel的频道音讯来更新新的主节点的相干信息(地址,端口号等)。

Sentinel在默认每隔2秒钟会发送Hello音讯包到其对应的主从实例的__sentinel__:hello频道中。Hello音讯格局如下:

__sentinel_:hello <sentinel地址> <sentinel端口号> <sentinel运行id> <sentinel配置纪元> <主节点名字 > <主节点地址> <主节点端口号> <主节点配置纪元>

当Sentinel通过订阅连贯收到其余Sentinel发送的的Hello包时,会更新对主从节点以及S发送Sentinel的认知,如果收到本人发送的Hello包,则简略的抛弃不做任何解决。这部分代码逻辑是在sentinel.c中的sentinelProcessHelloMessage函数中定义的,因为篇幅起因在这里不做具体介绍。

在sentinel.c中的sentinelReconnectInstance函数中,公布订阅连贯初始化如下:

is-master-down-by-addr 命令

Sentinel会默认每隔1s通过命令连贯发送is-master-down-by-addr命令以询问其余Sentinel节点对于监控的主节点是否下线。另外,在主实例下线的状况下,Sentinel之间也通过is-master-down-by-addr命令来取得投票并选举领头Sentinel。is-master-down-by-addr格局如下:

is-master-down-by-addr: <主实例地址> <主实例端口号> <以后配置纪元> <运行ID>

如果不是在选举领头Sentinel过程中, <runid>项总为*,相同地,如果在Sentinel向其余Sentinel发送投票申请状况下,<runid>项为本人的运行id。这部分代码如下:

is-master-down-by-addr的命令回复格局如下:

  • <主节点下线状态>
  • <领头Sentinel运行ID >
  • <领头Sentinel配置纪元>

Sentinel在收到其余Sentinel命令回复后,会记录其余Sentinel回复的主实例在线状态信息,以及在选举领头Sentinel过程中的投票状况,这部分的代码逻辑定义在sentinel.c中的sentinelReceiveIsMasterDownByReply函数:

Tilt模式

Sentinel的Tilt模式会在以下两种状况下开启:

  1. Sentinel过程被阻塞超过SENTINEL_TILT_TRIGGER工夫(默认为2s),可能因为过程或零碎I/O(内存,网络,存储)申请过多。
  2. 零碎时钟调整到之前某个工夫值。

Tilt模式是一种爱护机制,处于该模式下Sentinel除了发送必要的PING及INFO命令外,不会被动做其余操作,例如主备倒换,标记主观、主观下线等。但能够通过INFO 命令及公布订阅连贯的HELLO音讯包来获取外界信息并对本身构造进行更新,直到SENTINEL_TILT_PERIOD时长(默认为30s)完结为止,咱们能够认为Tilt模式是Sentinel的被动模式。

判断Tilt模式的代码逻辑定义如下:

参考资料:

ü https://github.com/antirez/redis

ü https://redis.io/topics/sentinel

ü Redis设计与实现第二版 黄健宏著

本文分享自华为云社区《Redis Sentinel 源码剖析》,原文作者:中间件小哥。

点击关注,第一工夫理解华为云陈腐技术~