1、sdown和odown转换机制
sdown和odown两种失败状态:
- sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机。sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机
- odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机。
sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机。
我们可以通过源码来看看哨兵是如何工作的:
首先,在定时任务severCron()
函数中,如果发现是哨兵则会执行哨兵的计时器sentinelTimer()
。
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
/* Run the Sentinel timer if we are in sentinel mode. */
if (server.sentinel_mode) sentinelTimer();
...
}
在sentinelTimer()
计时器中,会针对每一个哨兵进行处理,调用函数为sentinelHandleDictOfRedisInstances()
,检查所有的实例。
void sentinelTimer(void) {
...
sentinelHandleDictOfRedisInstances(sentinel.masters);
...
}
在sentinelHandleDictOfRedisInstances()
函数中,主要是递归遍历,调用sentinelHandleRedisInstance()
对实例进行处理。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
sentinelReconnectInstance(ri);
// 发送ping消息给这个实例
sentinelSendPeriodicCommands(ri);
if (sentinel.tilt) {
if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
sentinel.tilt = 0;
sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited");
}
// 判断是否主观下线
sentinelCheckSubjectivelyDown(ri);
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
/* Nothing so far. */
}
// 只针对master节点
if (ri->flags & SRI_MASTER) {
// 判断是都客观下线
sentinelCheckObjectivelyDown(ri);
if (sentinelStartFailoverIfNeeded(ri))
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
sentinelFailoverStateMachine(ri);
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
}
如何判断主观下线:
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
mstime_t elapsed = 0;
// act_ping_time:最后一个未完成的ping(之后没有收到pong)发出的时间。当接收到一个pong时,该字段被设置为0,如果该值为0并且发送了一个新的ping,则再次设置为当前时间。(说明已经出现ping过没回复)
if (ri->link->act_ping_time)
elapsed = mstime() - ri->link->act_ping_time;
else if (ri->link->disconnected)
// 连接不上,还没有出现ping过没回复
elapsed = mstime() - ri->link->last_avail_time;
// 如果检测到连接的活跃度(activity)很低,那么考虑重断开连接,并进行重连
if (ri->link->cc &&
(mstime() - ri->link->cc_conn_time) >
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
ri->link->act_ping_time != 0 && /* There is a pending ping... */
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
{
// 连接时长已超过最短连接间隔 ping已经发出,但在down_after_period/2的时间内没有收到pong响应
// 断开实例的cc连接(命令连接)
instanceLinkCloseConnection(ri->link,ri->link->cc);
}
// 最后一次从这个服务器接收消息的时间大于发布publish的最大时间的3倍,也就是6秒时断开订阅频道信息的连接
if (ri->link->pc &&
(mstime() - ri->link->pc_conn_time) >
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
(mstime() - ri->link->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3))
{
// 断开发布订阅的连接 pc
instanceLinkCloseConnection(ri->link,ri->link->pc);
}
//现在距离上次ping的时间已经超过了down_after_period,可以判定为主观宕机
if (elapsed > ri->down_after_period ||
(ri->flags & SRI_MASTER &&
ri->role_reported == SRI_SLAVE &&
mstime() - ri->role_reported_time >
(ri->down_after_period+SENTINEL_INFO_PERIOD*2)))
{
//判断为主观宕机
if ((ri->flags & SRI_S_DOWN) == 0) {
// 发送事件
sentinelEvent(LL_WARNING,"+sdown",ri,"%@");
// 记录主观宕机的时间
ri->s_down_since_time = mstime();
//更新主观宕机的状态
ri->flags |= SRI_S_DOWN;
}
} else {
/* 它活过来了,判定会不宕机*/
if (ri->flags & SRI_S_DOWN) {
// 发送事件
sentinelEvent(LL_WARNING,"-sdown",ri,"%@");
// 移除掉之前的宕机标识
ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
}
}
}
如何判断客观宕机,sentinelCheckObjectivelyDown(ri)
:
void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
dictIterator *di;
dictEntry *de;
unsigned int quorum = 0, odown = 0;
//当前的sentinel判断它为客观单价
if (master->flags & SRI_S_DOWN) {
/* Is down for enough sentinels? */
//需要满足条件
quorum = 1; /* the current sentinel. */
/* Count all the other sentinels. */
// 遍历所有的sentinel
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
if (ri->flags & SRI_MASTER_DOWN) quorum++;
}
//释放迭代器
dictReleaseIterator(di);
//是否满足认同数量大于等于quorum,满足则认为是客观宕机(odown)
if (quorum >= master->quorum) odown = 1;
}
/* 如果是odown*/
if (odown) {
if ((master->flags & SRI_O_DOWN) == 0) {
// 发送事件
sentinelEvent(LL_WARNING,"+odown",master,"%@ #quorum %d/%d",
quorum, master->quorum);
// 设置odown标识
master->flags |= SRI_O_DOWN;
// 记录事件
master->o_down_since_time = mstime();
}
} else {
if (master->flags & SRI_O_DOWN) {
// 没有进入odown,发送事件
sentinelEvent(LL_WARNING,"-odown",master,"%@");
// 移除标识
master->flags &= ~SRI_O_DOWN;
}
}
}
2、哨兵集群的自动发现机制
哨兵互相之间的发现,是通过redis的pub/sub系统实现的,每个哨兵都会往__sentinel__:hello
这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。
消息主要包括:
- s_ip: sentinel的ip
- s_port: sentinel的端口port
- s_runnid:sentinel的runnid
- s_epoch:纪元,版本
- m_name:master的名称
- m_ip:master的ip
- m_port:master 的端口
- m_epoch:master的epoch
每隔两秒钟,每个哨兵都会往自己监控的某个master+slaves
对应的__sentinel__:hello
channel里发送一个消息,内容是自己的host
、ip
和runid
还有对这个master
的监控配置
每个哨兵也会去监听自己监控的每个master+slaves对应的__sentinel__:hello channel,然后去感知到同样在监听这个master+slaves的其他哨兵的存在
每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步
3、slave配置的自动纠正
哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据; 如果slave连接到了一个错误的master上,比如故障转移之后,那么哨兵会确保它们连接到正确的master上
4、slave->master选举算法
如果一个master被认为odown了,而且majority哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来,优先级会考虑slave的一些信息。
- (1)跟master断开连接的时长,如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,也就是:(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state ,那么slave就被认为不适合选举为master,被排除掉。(这个仅仅是用于排除,不用于排序),剩下的执行后面的三个比较。
- (2)slave优先级,slave priority越低,优先级就越高,这个是可以进行配置的。默认是100,越小越优先。
- (3)复制offset,如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
- (4)run id,选择一个run id比较小的那个slave
5、quorum和majority
每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换
如果quorum < majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换
但是如果quorum >= majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换。
这个quorum是我们自己设置的, majority是客观存在的。
6、configuration epoch
哨兵会对一套redis master+slave进行监控,有相应的监控的配置,也就是可以理解为配置版本。
执行切换的那个哨兵,会从要切换到的新master(salve->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号。
7、configuraiton传播
哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub消息机制。要知道,主备切换是一个哨兵在干,干完了总得让别人知道是不是。
这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的。其他的哨兵会订阅这个消息,发现更新的epoch就认为master已经切换过了,接着把自己存的信息更新。这样,就可以保持哨兵里面的信息一致性了。
转载:https://blog.csdn.net/Aphysia/article/details/106309327