redis在启动时,如果进程名是"redis-sentinel",或者参数中带了"--sentinel",这时redis便以哨兵的方式运行。一个sentinel可以监控多个master。
sentinel的配置如下
// 当前Sentinel节点监控 127.0.0.1:6379 这个主节点
// 2代表判断主节点失败至少需要2个Sentinel节点节点同意
// mymaster是主节点的别名
sentinel monitor mymaster 127.0.0.1 6379 2
//每个Sentinel节点都要定期PING命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒且没有回复,则判定不可达
sentinel down-after-milliseconds mymaster 30000
//当Sentinel节点集合对主节点故障判定达成一致时,Sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作,限制每次向新的主节点发起复制操作的从节点个数为1
sentinel parallel-syncs mymaster 1
//故障转移超时时间为180000毫秒
sentinel failover-timeout mymaster 180000
sentinel支持的如下的命令列表:
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
sentinel的时间事件也区别于一般服务,入口是sentinelTimer。
void sentinelTimer(void) {
// 记录本次 sentinel 调用的事件,
// 并判断是否需要进入 TITL 模式
sentinelCheckTiltCondition();
// 执行定期操作
// 比如 PING 实例、分析主服务器和从服务器的 INFO 命令
// 向其他监视相同主服务器的 sentinel 发送问候信息
// 并接收其他 sentinel 发来的问候信息
// 执行故障转移操作,等等
sentinelHandleDictOfRedisInstances(sentinel.masters);
// 运行等待执行的脚本
sentinelRunPendingScripts();
// 清理已执行完毕的脚本,并重试出错的脚本
sentinelCollectTerminatedScripts();
// 杀死运行超时的脚本
sentinelKillTimedoutScripts();
/* We continuously change the frequency of the Redis "timer interrupt"
* in order to desynchronize every Sentinel from every other.
* This non-determinism avoids that Sentinels started at the same time
* exactly continue to stay synchronized asking to be voted at the
* same time again and again (resulting in nobody likely winning the
* election because of split brain voting). */
server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}
一次时间事件中,主要做了以下几件事情。
-
1.判断是否上次执行时间事件距今是否差了超过了2秒,则进入默认持续30秒的TITL模式,进入了此模式的sentinel认为当前自身的状态可能是有问题的,会阉割一部分选举master相关的功能。
-
2.连接其他master/slave/sentinel,发送auth命令,master的地址是配置文件配好的,所以可以直接拿到;slave和其他sentinel的地址通过master拿到。
-
3.向master和slave定期发送 ping,info命令。其中info命令可以得到对方的运行状态,主从关系等。
-
4.哨兵在连接建立的时候会向master和其slave订阅频道"_sentinel_:hello"的消息,同时周期性的发布一条hello消息,给其他哨兵打招呼,这条消息包含了自身地址,自身runid,自身的epoch,master的基本信息和epoch。
假设有个哨兵B也是检测这个master的,会接受到A发送的推送,把A保存到自己的哨兵列表中。 -
5.根据redis服务回复ping的情况,判断redis是否已经主观下线(Subject Down),如果下线了,将结果告知给其他sentinel。
-
6.如果master主观下线了,判断这个master是否客观下线(Object Down),方法就是收集其他sentinel的意见,如果认为该master下线的sentinel数量大于配置,则认为这个master下线了,此时开始执行故障转移逻辑(Failover)。
-
7.故障转移,分为以下步骤:
1)选举头领(leader)
1-1)首先向其他sentinel发送纪元编号(为本sentinel自身当前的纪元自增1,同时记录为故障master的纪元),如果其他纪元收到了这条消息,如果比自己的大,则更新为此纪元,否则忽略;
1-2)统计这个故障master下的所有sentinel投票结果,如果已经有其他sentinel发表意见了,则选取最高得票者,投他一票,如果没有,投自己一票。此时,如果最高得票者的得票数超过半数+1,则本次选举完成,否则进入下个纪元。引入纪元(epoch)这个参数是因为,选举不是每次都成功的,比如所有sentinel都选举了自己,这种情况下需要重新启动下一轮投票。
2)由leader从slave当中选择一个作为新的master。
2-1) 过滤掉Down掉的slave,判断slave的Info回复时长和与master的连接状态,太差的slave不考虑。
2-2) 对符合条件的slave排序以选出最优,依据分别是slave的优先级,从原master复制的数据量大小,runid。- 向选出来的slave发送 slaveof no one来提升该slave为master。确定该服务提升为master之后,给其他slave发送slaveof命令。
sentinel内部状态发生变化时,会调用sentinelEvent方法广播状态,我们可以使用redis-cli连接sentinel,用subscribe来查看sentinel的内部运行状态,可以订阅多个状态,用空格隔开,本机测试:
localhost:Debug jjchen$ redis-cli -p 26379
redis> subscribe +tilt -tilt
Reading messages... (press Ctrl-c to quit)
1. "subscribe"
2. "+tilt"
3. (integer) 1
1. "subscribe"
2. "-tilt"
3. (integer) 2
1. "message"
2. "+tilt"
3. "#tilt mode entered"
1. "message"
2. "-tilt"
3. "#tilt mode exited"
1. "message"
2. "+tilt"
3. "#tilt mode entered"