在的redis启动函数main(server.c文件)中,对哨兵模式进行了检查,如果是哨兵模式,将调用initSentinelConfig和initSentinel进行初始化,initServer函数中会注册哨兵的时间事件,最后调用sentinelIsRunning运行哨兵实例,
1 | int main(int argc, char **argv) { |
哨兵初始化
哨兵模式校验
checkForSentinelMode
checkForSentinelMode函数在server.c文件中,用于校验是否是哨兵模式,可以看到有两种方式校验哨兵模式:
- 直接执行
redis-sentinel
命令 - 执行
redis-server
命令,命令参数中带有–sentinel
参数
1 | int checkForSentinelMode(int argc, char **argv) { |
初始化配置项
initSentinelConfig
initSentinelConfig函数在sentinel.c文件中,用于初始化哨兵配置:
- 将哨兵实例的端口号设置为
REDIS_SENTINEL_PORT
,默认值26379 - 将
protected_mode
设置为0,表示允许外部链接哨兵实例,而不是只能通过127.0.0.1本地连接 server
1 |
|
initSentinel
在看initSentinel函数之前,首先看下Redis中哨兵sentinel对象对应的结构体sentinelState:
- current_epoch:当前纪元,投票选举Leader时使用,纪元可以理解为投票的轮次
- masters:监控的master节点哈希表,Key为master节点名称, value为master节点对应sentinelRedisInstance实例的指针
1 | struct sentinelState { |
sentinelRedisInstance是一个通用的结构体,在sentinel.c文件中定义,它既可以表示主节点,也可以表示从节点或者其他哨兵实例,从中选选出了一些主要的内容:
1 | typedef struct { |
initSentinel函数同样在sentinel.c文件中,用于初始化哨兵,由于哨兵实例与普通Redis实例不一样,所以需要替换Redis中的命令,添加哨兵实例命令,哨兵实例使用的命令在sentinelcmds中定义:
- 将server.commands和server.orig_commands保存的常规Redis命令清除
- 遍历sentinelcmds哨兵实例专用命令,将命令添加到server.commands和server.orig_commands中
- 初始化sentinel实例中的数据项
1 | // 哨兵实例下的命令 |
启动哨兵实例
sentinelIsRunning
sentinelIsRunning函数在sentinel.c文件中,用于启动哨兵实例:
- 校验是否设置了哨兵实例的ID,如果未设置,将随机生成一个ID
- 调用sentinelGenerateInitialMonitorEvents向监控的主节点发送+monitor事件
1 | void sentinelIsRunning(void) { |
哨兵时间事件
在initServer函数中,调用aeCreateTimeEvent注册了时间事件,周期性的执行serverCron函数,serverCron函数中通过server.sentinel_mode判断是否是哨兵模式,如果是哨兵模式,调用sentinelTimer执行哨兵事件:
1 | void initServer(void) { |
sentinelTimer
sentinelTimer在sentinel.c文件中,sentinelTimer函数会周期性的执行:
1 | void sentinelTimer(void) { |
sentinelHandleDictOfRedisInstances
sentinelHandleDictOfRedisInstances函数中会对传入的当前哨兵实例监听的主节点哈希表进行遍历:
- 获取哈希表中的每一个节点,节点类型是sentinelRedisInstance
- 调用sentinelHandleRedisInstance检测哨兵监听节点的状态
- 如果sentinelHandleRedisInstance是主节点,由于主节点里面保存了监听该主节点的其他哨兵实例以及从节点,所以递归调用sentinelHandleDictOfRedisInstances对其他的节点进行检测
1 | /* sentinelHandleDictOfRedisInstances */ |
检测哨兵监听的节点状态
sentinelHandleRedisInstance
sentinelHandleRedisInstance函数对传入的sentinelRedisInstance实例,进行状态检测,主要处理逻辑如下:
- 调用sentinelReconnectInstance对实例的连接状态进行判断,如果连接中断尝试重新与实例建立连接
- 调用sentinelSendPeriodicCommands向实例发送PING INFO等命令
- sentinelCheckSubjectivelyDown判断实例是否主观下线
- 如果实例是master节点,调用sentinelCheckObjectivelyDown判断是否客观下线、是否需要执行故障切换
1 |
|
重新连接
sentinelReconnectInstance
sentinelReconnectInstance函数用于检测实例的连接状态,如果中断进行重连,主要处理逻辑如下:
检测连接是否中断,如果未中断直接返回
检查端口是否为0,0被认为是不合法的端口
从sentinelRedisInstance实例中获取instanceLink,instanceLink的定义在sentinel.c文件中,里面记录了哨兵和主节点的两个连接,分别为用于发送命令的连接cc和用于发送Pub/Sub消息的连接pc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15typedef struct instanceLink {
int refcount;
int disconnected;
int pending_commands;
redisAsyncContext *cc; /* 用于发送命令的连接 */
redisAsyncContext *pc; /* 用于发送Pub/Sub消息的连接 */
mstime_t cc_conn_time; /* cc 连接时间 */
mstime_t pc_conn_time; /* pc 连接时间 */
mstime_t pc_last_activity;
mstime_t last_avail_time; /* 上一次收到实例回复PING命令(需要被认定为合法)的时间 */
mstime_t act_ping_time; /* 当收到PONG消息的时候会设置为0,在下次发送PING命令时设置为当前时间 */
mstime_t last_ping_time; /* 上次发送PING命令时间,在出现故障时可以通过判断发送时间避免多次发送PING命令 */
mstime_t last_pong_time; /* 上次收到PONG消息的时间 */
mstime_t last_reconn_time; /* 上次执行重连的时间 */
} instanceLink;校验距离上次重连时间是否小于PING的检测周期SENTINEL_PING_PERIOD,如果小于说明距离上次重连时间过近,直接返回即可
SENTINEL_PING_PERIOD在server.c中定义,默认1000毫秒
1
对用于发送命令的连接判断,如果连接为NULL,调用redisAsyncConnectBind函数进行重连
对用于处理发送 Pub/Sub 消息的连接进行判断,如果连接为NULL,调用redisAsyncConnectBind函数进行重连
1 | void sentinelReconnectInstance(sentinelRedisInstance *ri) { |
发送命令
sentinelSendPeriodicCommands
sentinelSendPeriodicCommands用于向实例发送命令:
1 | void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) { |
主观下线
sentinelCheckSubjectivelyDown
sentinelCheckSubjectivelyDown函数用于判断是否主观下线。
标记主观下线的两个条件
- 距离上次发送PING命令的时长超过了down_after_period的值,down_after_period的值在sentinel.conf 配置文件中配置,对应的配置项为down-after-milliseconds ,默认值30s
- 哨兵认为实例是主节点(ri->flags & SRI_MASTE),但是实例向哨兵返回的角色是从节点(ri->role_reported == SRI_SLAVE) 并且当前时间-实例报告消息的时间role_reported_time大于down_after_period加上SENTINEL_INFO_PERIOD乘以2的时间 ,SENTINEL_INFO_PERIOD 是发送INFO命令的时间间隔,也就是说实例上次成功向哨兵报告角色的时间,已经超过了限定时间(down_after_period加上SENTINEL_INFO_PERIOD*2)
满足以上两个条件之一哨兵将会把sentinelRedisInstance判断为主观下线,flag标记会添加SRI_S_DOWN状态。
1 | void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) { |
客观下线
如果是主节点,将会调用sentinelCheckObjectivelyDown函数判断客观下线,之后调用sentinelStartFailoverIfNeeded判断是否需要执行故障切换。
总结
参考
Redis版本:redis-6.2.5