摘要:本文通过对 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 函数会监测以下两种条件:
- 程序应用 redis-sentinel 可执行文件执行。
- 程序参数列表中有 –sentinel 标记。
以上任何一种条件成立则 Redis 会应用 Sentinel 的形式运行。
在 Redis 判断是否以 Sentinel 的形式运行当前,咱们会看到如下代码段:
在 initSentinelConfig 函数中,会应用 Sentinel 特定的端口(默认为 26379)来代替 Redis 的默认端口(6379)。另外,在 Sentinel 模式下,须要禁用服务器运行保护模式。
与此同时,initSentinel 函数会做如下操作:
1. 应用 Sentinel 自带的命令表去代替 Redis 服务器原生的命令. Sentinel 反对的命令表如下:
2. 初始化 Sentinel 主状态构造,Sentinel 主状态的定义及正文如下。
其中 masters 字典指针中的每个值都对应着一个 Sentinel 检测的主实例。
在读取配置信息后,Redis 服务器主函数会调用 sentinelIsRunning 函数,做以下几个工作:
- 查看配置文件是否被设置,并且检查程序对配置文件是否有写权限,因为如果 Sentinel 状态扭转的话,会一直将本人以后状态记录在配置文件中。
- 如果在配置文件中指定运行 ID,Sentinel 会应用这个 ID 作为运行 ID,相同地,如果没有指定运行 ID,Sentinel 会生成一个 ID 用来作为 Sentinel 的运行 ID。
- 对所有的 Sentinel 监测实例产生初始监测事件。
Sentinel的主工夫事件函数
Sentinel 应用和 Redis 服务器雷同的事件处理机制:分为文件事件和工夫事件。文件事件解决机制应用 I /O 多路复用来解决服务器端的网络 I /O 申请,例如客户端连贯,读写等操作。工夫解决机制则在主循环中周期性调用工夫函数来解决定时操作,例如服务器端的保护,定时更新,删除等操作。Redis 服务器主工夫函数是在 server.c 中定义的 serverCron 函数, 在默认状况下,serverCron 会每 100ms 被调用一次。在这个函数中,咱们看到如下代码:
其中当服务器以 sentinel 模式运行的时候,serverCron 会调用 sentinelTimer 函数,来运行 Sentinel 中的主逻辑,sentinelTimer 函数在 sentinel.c 中的定义如下:
Sentinel Timer 函数会做如下几个操作:
- 查看 Sentinel 以后是否在 Tilt 模式(Tilt 模式将会在稍后章节介绍)。
- 查看 Sentinel 与其监控主备实例,以及其余 Sentinel 实例的连贯,更新以后状态,并在主实例下线的时候主动做主备倒换操作。
- 查看回调脚本状态,并做相应操作。
- 更新服务器频率(调用 serverCron 函数的频率),加上一个随机因子,作用是避免监控雷同主节点的 Sentinel 在选举 Leader 的时候工夫抵触,导致选举无奈产生相对多的票数。
其中 SentinelHandleDictOfRedisInstances 函数的定义如下:
SentinelHandleDictOfRedisInstances 函数次要做的工作是:
1. 调用 sentinelHandleDictOfRedisInstance 函数解决 Sentinel 与其它特定实例连贯,状态更 新,以及主备倒换工作。
- 如果以后解决实例为主实例,递归调用 SentinelHandleDictOfRedisInstances 函数解决其上司的从实例以及其余监控这个主实例的 Sentinel。
- 在主备倒换胜利的状况下,更新主实例为降级为主实例的从实例。
其中在 sentinelHandleRedisInstance 的定义如下:
这个函数会做以下两局部操作:
1. 查看 Sentinel 和其余实例(主备实例以及其余 Sentinel)的连贯,如果连贯没有设置或曾经断开连接,Sentinel 会重试绝对应的连贯,并定时发送响应命令。须要留神的是:Sentinel和每个主备实例都有两个连贯,命令连贯和公布订阅连贯 。然而与 其余监听雷同主备实例的 Sentinel 只保留命令连贯,这部分细节会在网络章节独自介绍。
2. 第二局部操作次要做的是监测主备及其他 Sentinel 实例,并监测其是否在主观下线状态,对于主实例来说,还要检测是否在主观下线状态,并进行相应的主备倒换操作。
须要留神的是第二局部操作如果 Sentinel 在 Tilt 模式下是疏忽的,上面咱们来看一下这个函数第二局部的的具体实现细节。
sentinelCheckSubjectivelyDown 函数会监测特定的 Redis 实例(主备实例以及其余 Sentinel)是否处于主观下线状态,这部分函数代码如下:
主观下线状态意味着特定的 Redis 实例满足以下条件之一:
- 在实例配置的 down_after_milliseconds 工夫内没有收到 Ping 的回复。
- 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 实例通过发送接管命令的形式进行通信,例如:
- Sentinel 会默认以每 1s 距离发送 PING 命令给其余实例以主观判断其余实例是否下线。
- Sentinel 会通过 Sentinel 和主实例之间的命令连贯每隔 10s 发送 INFO 命令给主从实例以失去主实例和从实例的最新信息。
- 在主实例下线的状况下,Sentinel 会通过 Sentinel 和从实例的命令连贯发送 SLAVEOF NO ONE 命令给选定的从实例从而使从实例晋升为新的主节点。
- 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 模式会在以下两种状况下开启:
- Sentinel 过程被阻塞超过 SENTINEL_TILT_TRIGGER 工夫(默认为 2s),可能因为过程或零碎 I /O(内存,网络,存储)申请过多。
- 零碎时钟调整到之前某个工夫值。
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 源码剖析》,原文作者:中间件小哥。
点击关注,第一工夫理解华为云陈腐技术~