本章重点走读 redis 源码,理解 sentinel 检测 master 节点的主客观下线流程。
redis 哨兵集群有 3 个角色:sentinel/master/slave,每个角色都可能出现故障,故障转移主要针对 master
:
1
master 主观下线 --> master 客观下线 --> 投票选举 leader --> leader 执行故障转移。
1. 故障转移流程
- sentinel 时钟定时检查监控的各个 redis 实例角色,是否通信异常。
- 发现 master 主观下线。
- 向其它 sentinel 节点询问它们是否也检测到该 master 已下线。
- sentinel 通过向其它 sentinel 节点询问,确认 master 客观下线。
- 进入选举环节,sentinel 向其它 sentinel 节点拉票,希望它们选自己为代表进行故障转移。
- 少数服从多数,在最新一轮选举中,当超过法定个数的 sentinel 选择某个 sentinel 为代表,那么该 sentienl 当选 leader。
- sentinel 代表执行故障转移。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
...
/* 检查 sentinel 是否处在异常状态,例如本地时间忽然改变,因为心跳通信等,依赖时间。*/
if (sentinel.tilt) {
if (mstime() - sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
sentinel.tilt = 0;
sentinelEvent(LL_WARNING, "-tilt", NULL, "#tilt mode exited");
}
/* 检查所有节点类型 sentinel/master/slave,是否主观下线。*/
sentinelCheckSubjectivelyDown(ri);
...
if (ri->flags & SRI_MASTER) {
/* 检查 master 是否客观下线。 */
sentinelCheckObjectivelyDown(ri);
/* 是否满足故障转移条件,开启故障转移。 */
if (sentinelStartFailoverIfNeeded(ri))
/* 满足条件,进入故障转移环节,马上向其它 sentinel 节点选举拉票。 */
sentinelAskMasterStateToOtherSentinels(ri, SENTINEL_ASK_FORCED);
/* 通过状态机,处理故障转移对应各个环节。 */
sentinelFailoverStateMachine(ri);
/* 定时向其它 sentinel 节点询问 master 主观下线状况或选举拉票。 */
sentinelAskMasterStateToOtherSentinels(ri, SENTINEL_NO_FLAGS);
}
}
2. 故障发现
2.1. 主观下线
主要检查节点间的 心跳 通信是否正常。
- 检测异步链接是否超时,超时则关闭链接。
- 检测心跳是否超时,超时则标识主观下线,否则恢复正常。
- master 角色误报,超时标识主观下线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
mstime_t elapsed = 0;
/* 通过心跳通信间隔判断掉线逻辑。 */
if (ri->link->act_ping_time)
elapsed = mstime() - ri->link->act_ping_time;
else if (ri->link->disconnected)
elapsed = mstime() - ri->link->last_avail_time;
/* tcp 异步链接通信超时关闭对应链接。 */
...
/* 主观下线
* 1. 心跳通信超时。
* 2. 主服务节点却上报从服务角色,异常情况超时。 */
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))) {
/* Is subjectively down */
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);
}
}
}
2.2. 客观下线
2.2.1. 询问主观下线
当 sentinel 检测到 master 主观下线,它会询问其它 sentinel(发送 IS-MASTER-DOWN-BY-ADDR 请求):是否也检测到该 master 已经下线。
SENTINEL IS-MASTER-DOWN-BY-ADDR
命令有两个作用:
- 询问其它 sentinel 节点,该 master 是否已经下线,命令最后一个参数为 <*>。
- 确认 master 客观下线,当前 sentinel 向其它 sentinel 拉选票,让其它 sentinel 选自己为 “代表”。命令最后一个参数为 <sentinel_runid>,sentinel 自己的 runid。
这里是 sentinel 发现了 master 主观下线,所以先进入询问环节,再进行选举拉票。
1
2
# is-master-down-by-addr 命令格式。
SENTINEL is-master-down-by-addr <masterip> <masterport> <sentinel.current_epoch> <*>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* If we think the master is down, we start sending
* SENTINEL IS-MASTER-DOWN-BY-ADDR requests to other sentinels
* in order to get the replies that allow to reach the quorum
* needed to mark the master in ODOWN state and trigger a failover. */
#define SENTINEL_ASK_FORCED (1 << 0)
void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
dictIterator *di;
dictEntry *de;
di = dictGetIterator(master->sentinels);
while ((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
...
/* Only ask if master is down to other sentinels if:
*
* 1) We believe it is down, or there is a failover in progress.
* 2) Sentinel is connected.
* 3) We did not receive the info within SENTINEL_ASK_PERIOD ms. */
if ((master->flags & SRI_S_DOWN) == 0) continue;
if (ri->link->disconnected) continue;
if (!(flags & SENTINEL_ASK_FORCED) &&
mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD)
continue;
/* 当 sentinel 检测到 master 主观下线,那么参数发送 "*",等待确认客观下线,
* 当确认客观下线后,再进入选举环节。sentinel 再向其它 sentinel 发送自己的 runid,去拉票。*/
ll2string(port, sizeof(port), master->addr->port);
retval = redisAsyncCommand(ri->link->cc,
sentinelReceiveIsMasterDownReply, ri,
"%s is-master-down-by-addr %s %s %llu %s",
sentinelInstanceMapCommand(ri, "SENTINEL"),
master->addr->ip, port,
sentinel.current_epoch,
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE)
? sentinel.myid : "*");
if (retval == C_OK) ri->link->pending_commands++;
}
dictReleaseIterator(di);
}
2.2.2. 其它 sentinel 接收命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void sentinelCommand(client *c) {
...
else if (!strcasecmp(c->argv[1]->ptr, "is-master-down-by-addr")) {
...
/* 其它 sentinel 接收到询问命令,根据 ip 和 端口查找对应的 master。 */
ri = getSentinelRedisInstanceByAddrAndRunID(
sentinel.masters, c->argv[2]->ptr, port, NULL);
/* 当前 sentinel 如果没有处于异常保护状态,而且也检测到询问的 master 已经主观下线了。 */
if (!sentinel.tilt && ri && (ri->flags & SRI_S_DOWN) && (ri->flags & SRI_MASTER))
isdown = 1;
/* 询问 master 主观下线命令参数是 *,选举投票参数是请求的 sentinel 的 runid。*/
if (ri && ri->flags & SRI_MASTER && strcasecmp(c->argv[5]->ptr, "*")) {
leader = sentinelVoteLeader(
ri, (uint64_t)req_epoch, c->argv[5]->ptr, &leader_epoch);
}
/* 根据询问主观下线或投票选举业务确定回复的内容参数。 */
addReplyArrayLen(c, 3);
addReply(c, isdown ? shared.cone : shared.czero);
addReplyBulkCString(c, leader ? leader : "*");
addReplyLongLong(c, (long long)leader_epoch);
if (leader) sdsfree(leader);
}
...
}
2.2.3. 当前 sentinel 接收命令回复
当前 sentinel 接收到询问的回复,如果确认该 master 已经主观下线,那么将其标识为 SRI_MASTER_DOWN
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Receive the SENTINEL is-master-down-by-addr reply, see the
* sentinelAskMasterStateToOtherSentinels() function for more information. */
void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) {
...
if (r->type == REDIS_REPLY_ARRAY && r->elements == 3 &&
r->element[0]->type == REDIS_REPLY_INTEGER &&
r->element[1]->type == REDIS_REPLY_STRING &&
r->element[2]->type == REDIS_REPLY_INTEGER) {
ri->last_master_down_reply_time = mstime();
if (r->element[0]->integer == 1) {
/* ri sentinel 回复,也检测到该 master 节点已经主观下线。 */
ri->flags |= SRI_MASTER_DOWN;
} else {
ri->flags &= ~SRI_MASTER_DOWN;
}
...
}
}
2.2.4. 确认客观下线
当大于等于法定个数(quorum)的 sentinel 节点确认该 master 主观下线,那么标识该主观下线的 master 为客观下线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
dictIterator *di;
dictEntry *de;
unsigned int quorum = 0, odown = 0;
if (master->flags & SRI_S_DOWN) {
/* Is down for enough sentinels? */
quorum = 1; /* the current sentinel. *
/* Count all the other sentinels. */
di = dictGetIterator(master->sentinels);
while ((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
/* 该 ri 检测到 master 主观掉线。 */
if (ri->flags & SRI_MASTER_DOWN) {
quorum++;
}
}
dictReleaseIterator(di);
/* 是否满足当前 sentinel 配置的法定个数:quorum。 */
if (quorum >= master->quorum) {
odown = 1;
}
}
/* Set the flag accordingly to the outcome. */
if (odown) {
if ((master->flags & SRI_O_DOWN) == 0) {
sentinelEvent(LL_WARNING, "+odown", master, "%@ #quorum %d/%d",
quorum, master->quorum);
master->flags |= SRI_O_DOWN;
master->o_down_since_time = mstime();
}
} else {
if (master->flags & SRI_O_DOWN) {
sentinelEvent(LL_WARNING, "-odown", master, "%@");
master->flags &= ~SRI_O_DOWN;
}
}
}
3. 开启故障转移
当 sentinel 检测到某个 master 客观下线,开启故障转移流程,进入选举环节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* 定时检查 master 故障情况情况。 */
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
...
if (ri->flags & SRI_MASTER) {
/* 检查 master 是否客观下线。 */
sentinelCheckObjectivelyDown(ri);
/* 是否满足故障转移条件,开启故障转移。 */
if (sentinelStartFailoverIfNeeded(ri))
/* 满足条件,进入故障转移环节,马上向其它 sentinel 节点选举拉票。 */
sentinelAskMasterStateToOtherSentinels(ri, SENTINEL_ASK_FORCED);
/* 通过状态机,处理故障转移对应各个环节。 */
sentinelFailoverStateMachine(ri);
/* 定时向其它 sentinel 节点询问 master 主观下线状况或选举拉票。 */
sentinelAskMasterStateToOtherSentinels(ri, SENTINEL_NO_FLAGS);
}
}
/* 是否满足故障转移条件,开启故障转移。 */
int sentinelStartFailoverIfNeeded(sentinelRedisInstance *master) {
/* master 客观下线。 */
if (!(master->flags & SRI_O_DOWN)) return 0;
/* 当前 master 没有处在故障转移过程中。 */
if (master->flags & SRI_FAILOVER_IN_PROGRESS) return 0;
/* 两次故障转移,需要有一定的时间间隔。
* 1. 当前 sentinel 满足了故障转移条件。
* 2. 当前 sentinel 接收到其它 sentinel 的拉票,也设置了 failover_start_time,说明
* 其它 sentinel 先开启了故障转移,为了避免冲突,需要等待一段时间。*/
if (mstime() - master->failover_start_time < master->failover_timeout * 2) {
...
return 0;
}
sentinelStartFailover(master);
return 1;
}
/* 开启故障转移,进入投票环节。 */
void sentinelStartFailover(sentinelRedisInstance *master) {
...
/* 当前 master 开启故障转移。 */
master->failover_state = SENTINEL_FAILOVER_STATE_WAIT_START;
/* 当前 master 故障转移正在进行中。 */
master->flags |= SRI_FAILOVER_IN_PROGRESS;
/* 开始一轮选举,选举纪元(计数器 + 1)。*/
master->failover_epoch = ++sentinel.current_epoch;
...
/* 记录故障转移开启时间。 */
master->failover_start_time = mstime() + rand() % SENTINEL_MAX_DESYNC;
master->failover_state_change_time = mstime();
}