Redis - 哨兵
哨兵是 Redis 解决高可用的方案,以下主要以源码的方式说明。
架构
哨兵监控主节点、多个从节点、以及其他哨兵。一般来说,一个哨兵集群只监控一套主从(即一个主节点及其多个从节点),但也支持监控多套主从,但这多套主从没有任何关系,是独立的,只是共用了一套哨兵集群而已。所以哨兵的典型架构,是多个哨兵、一个主节点、多个从节点。
核心数据结构以及状态
/*被监视实例的角色*/
#define SRI_MASTER (1<<0) //实例是一个主服务器
#define SRI_SLAVE (1<<1) //实例是一个从服务器
#define SRI_SENTINEL (1<<2) //实例是一个Sentinel
/*被监视实例的状态*/
#define SRI_DISCONNECTED (1<<3) //实例已断线
#define SRI_S_DOWN (1<<4) //实例已主观下线
#define SRI_O_DOWN (1<<5) //实例已客观下线
#define SRI_MASTER_DOWN (1<<6) //sentinel认为主节点已经下线
/*故障转移过程中的状态*/
#define SRI_FAILOVER_IN_PROGRESS (1<<7) //当前主节点正在进行故障转移
#define SRI_PROMOTED (1<<8) //当前从节点被选中晋升为主节点
#define SRI_RECONF_SENT (1<<9) //当前从节点正在slaveof到新主节点
#define SRI_RECONF_INPROG (1<<10) //当前从节点正在从新主节点同步
#define SRI_RECONF_DONE (1<<11) //当前从节点已从新主节点同步完成,开始复制
#define SRI_FORCE_FAILOVER (1<<12) //强制主节点进行故障转移
哨兵会为每个被监控的对象创建一个实例,维护其相关的信息。
typedef struct sentinelRedisInstance {
/*实例的状态信息*/
int flags; // 实例标识,表示实例类型和状态,取值如上SRI开头的状态
char *runid; // 实例的运行 ID
uint64_t config_epoch; // 配置纪元,用于实现故障转移
/*实例的通信地址*/
sentinelAddr *addr; // 实例的地址
redisAsyncContext *cc; // 用于发送命令的异步连接
redisAsyncContext *pc; // 用于执行 SUBSCRIBE 命令、接收频道信息的异步连接
/*拓扑信息*/
dict *sentinels; // 其他同样监控这个主服务器的所有 sentinel
dict *slaves; //如果这个实例代表的是一个主服务器,那么这个字典保存着主服务器属下的从服务器
/*故障转移相关*/
// SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的 quorum 参数
int quorum; //判断这个实例为客观下线(objectively down)所需的支持投票数量
int slave_priority; // 从服务器优先级
struct sentinelRedisInstance *master; // 主服务器的实例(在本实例为从服务器时使用)
char *slave_master_host; // INFO 命令的回复中记录的主服务器 IP
int slave_master_port; // INFO 命令的回复中记录的主服务器端口号
int slave_master_link_status; // INFO 命令的回复中记录的主从服务器连接状态
unsigned long long slave_repl_offset; // 从服务器的复制偏移量
char *leader; /* If this is a master instance, this is the runid of
the Sentinel that should perform the failover. If
this is a Sentinel, this is the runid of the Sentinel
that this Sentinel voted as leader. */
uint64_t leader_epoch; /* Epoch of the 'leader' field. */
uint64_t failover_epoch; /* Epoch of the currently started failover. */
} sentinelRedisInstance;
程序流程
入口
//sentinel模式的入口,由定时器定期调用
void sentinelTimer(void) {
// 并判断是否需要进入 TITL 模式
//两次调用的时间差为负数或者相隔很大(正常一般100ms)则进入TILT,意味着系统出现明显问题,此时哨兵
//仍然记录信息,但是不做下线判断、故障转移等处理。等待固定时间之后再开始处理,以免错误判断
sentinelCheckTiltCondition();
// 执行定期操作,比如 PING 实例、分析主服务器和从服务器的 INFO 命令
// 向其他监视相同主服务器的 sentinel 发送问候信息,并接收其他 sentinel 发来的问候信息
// 执行故障转移操作,等等
// 该函数是sentinel模式下最核心的逻辑
sentinelHandleDictOfRedisInstances(sentinel.masters);
/**********下面是一些脚本的管理不用太关注*******************/
// 运行等待执行的脚本
sentinelRunPendingScripts();
// 清理已执行完毕的脚本,并重试出错的脚本
sentinelCollectTerminatedScripts();
// 杀死运行超时的脚本
sentinelKillTimedoutScripts();
server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}
监控 &处理范围
主逻辑主要位于函数:sentinelHandleDictOfRedisInstances 中,该函数的入参是 master 实例,在函数内部有对 slaves 和 sentinels 进行了相同函数调用。从这里看到,哨兵对 master、slaves、sentinels 都进行了监控、处理。
void sentinelHandleDictOfRedisInstances(dict *instances) {
dictIterator *di;
dictEntry *de;
sentinelRedisInstance *switch_to_promoted = NULL;
/* There are a number of things we need to perform against every master. */
// 遍历多个实例,这些实例可以是多个主服务器、多个从服务器或者多个 sentinel
di = dictGetIterator(instances);
while((de = dictNext(di)) != NULL) {
// 取出实例对应的实例结构
sentinelRedisInstance *ri = dictGetVal(de);
// 执行调度操作
sentinelHandleRedisInstance(ri);
// 如果被遍历的是主服务器,那么递归地遍历该主服务器的所有从服务器
// 以及所有 sentinel
if (ri->flags & SRI_MASTER) {
// 所有从服务器
sentinelHandleDictOfRedisInstances(ri->slaves);
// 所有 sentinel
sentinelHandleDictOfRedisInstances(ri->sentinels);
// 对已下线主服务器(ri)的故障迁移已经完成
// ri 的所有从服务器都已经同步到新主服务器
if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {
// 已选出新的主服务器
switch_to_promoted = ri;
}
}
}
// 将原主服务器(已下线)从主服务器表格中移除,并使用新主服务器代替它
if (switch_to_promoted)
sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);
dictReleaseIterator(di);
}
核心逻辑
核心逻辑是针对 master、slave、sentinel 的,有些操作根据类型不同做了分支处理。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
/* ========== 监控操作 =========*/
/* 对所有类型实例进行处理 */
//如果连接cc/pc未建立,则创建,未订阅__sentinel__:hello频道,则订阅(只有入参实例是主/从节点时才订阅)
//cc用于当前sentinel向入参的实例发送异步命令,pc用于接收消息(基于redis的订阅发布机制)
sentinelReconnectInstance(ri);
// 根据情况,向实例发送 PING、 INFO 或者 PUBLISH 命令,对于响应异步处理
sentinelSendPeriodicCommands(ri);
/* ============== 故障检测 ============= */
/* Every kind of instance */
// 检查给定实例是否进入 SDOWN 状态
sentinelCheckSubjectivelyDown(ri);
/* Masters and slaves */
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
/* Nothing so far. */
}
/* Only masters */
/* 对主服务器进行处理 */
if (ri->flags & SRI_MASTER) {
// 判断 master 是否进入 ODOWN 状态
sentinelCheckObjectivelyDown(ri);
// 如果主服务器进入了 ODOWN 状态,那么开始一次故障转移操作
if (sentinelStartFailoverIfNeeded(ri))
// 强制向其他 Sentinel 发送 SENTINEL is-master-down-by-addr 命令
// 刷新其他 Sentinel 关于主服务器的状态
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
// 执行故障转移
sentinelFailoverStateMachine(ri);
// 这里是主观下线后,本sentinel向其他sentinel询问主观下线情况的逻辑。代码写在这里很容易让人误解
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
}
三个定时任务
函数 sentinelSendPeriodicCommands(ri)向入参实例发送 ping、info、publish 命令,异步处理实例的响应,以掌握实例的状态,为后续的下线判断、处理提供依据。以下非核心的部分代码不呈现。
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
// 实例不是 Sentinel (主服务器或者从服务器)
// 并且以下条件的其中一个成立:
// 1)SENTINEL 未收到过这个服务器的 INFO 命令回复
// 2)距离上一次该实例回复 INFO 命令已经超过 info_period 间隔
// 那么向实例发送 INFO 命令
if ((ri->flags & SRI_SENTINEL) == 0 &&
(ri->info_refresh == 0 ||
(now - ri->info_refresh) > info_period))
{
/* Send INFO to masters and slaves, not sentinels. */
retval = redisAsyncCommand(ri->cc,
sentinelInfoReplyCallback, NULL, "INFO");
//这里的回调处理info响应,更新入参实例的信息,主要包括主/从节点的状态、地址、runid、主从拓扑(
//比如新加入的从节点)
if (retval == REDIS_OK) ri->pending_commands++;
} else if ((now - ri->last_pong_time) > ping_period) {
/* Send PING to all the three kinds of instances. */
sentinelSendPing(ri);
//发送ping命令探测入参实例运行状态是否正常,内部也设置为回调,更新入参实例的信息,主要包括
//入参实例是否正常,以及相关的各种时间,比如最后一次实例运行正常的时间、最后一次响应ping的时间
//这些是后续主观下线判断的依据
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
/* PUBLISH hello messages to all the three kinds of instances. */
//虽然这里向3种类型的实例发送了消息,但sentinel只会订阅master和slave的消息,所以即便当前
//哨兵向其他哨兵发送了消息,也没有任何作用,因为没有消费者
sentinelSendHello(ri);
//这里发送hello信息的作用:
//1、广播master的ip、port、runid,以便其他新加入的哨兵能够知晓
//2、广播哨兵的ip、port、epoch,也让其他哨兵知晓。后续故障转移是需要哨兵们投票的,所以每个哨兵需要知道哨兵集群情况
//这里也有回调,但只是更新消息发送的时间,比较简单
}
}
通过定时任务的方式,将本 sentinel 和其他受监控实例的通信过程、实例状态和信息的维护和之后的逻辑判断解耦。
主观下线判断
当前 sentinel 自己判断入参实例是否下线,称为主观下线判断。
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
mstime_t elapsed = 0;
if (ri->last_ping_time)
//用当前时间 - 最后一次ping时间的间隔,来作为主观下线的判断依据
elapsed = mstime() - ri->last_ping_time;
/* Update the SDOWN flag. We believe the instance is SDOWN if:
*
* 更新 SDOWN 标识。如果以下条件被满足,那么 Sentinel 认为实例已下线:
*
* 1) It is not replying.
* 它没有回应命令
* 2) We believe it is a master, it reports to be a slave for enough time
* to meet the down_after_period, plus enough time to get two times
* INFO report from the instance.
* Sentinel 认为实例是主服务器,这个服务器向 Sentinel 报告它将成为从服务器,
* 但在超过给定时限之后,服务器仍然没有完成这一角色转换。
*/
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) {
// 发送事件,这里的事件是不牵扯业务的,只是记录,其他sentinel也不会使用
sentinelEvent(REDIS_WARNING,"+sdown",ri,"%@");
// 记录进入 SDOWN 状态的时间
ri->s_down_since_time = mstime();
// 打开 SDOWN 标志
ri->flags |= SRI_S_DOWN;
}
} else {
// 移除(可能有的) SDOWN 状态
/* Is subjectively up */
if (ri->flags & SRI_S_DOWN) {
// 发送事件
sentinelEvent(REDIS_WARNING,"-sdown",ri,"%@");
// 移除相关标志
ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
}
}
}
客观下线判断
当前 sentinel 判断主节点主观下线后,则会开始客观下线判断。客观下线判断逻辑如下。
void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
dictIterator *di;
dictEntry *de;
int quorum = 0, odown = 0;
// 如果当前 Sentinel 将主服务器判断为主观下线
// 那么检查是否有其他 Sentinel 同意这一判断
// 当同意的数量足够时,将主服务器判断为客观下线
if (master->flags & SRI_S_DOWN) {
/* Is down for enough sentinels? */
// 统计同意的 Sentinel 数量(起始的 1 代表本 Sentinel)
quorum = 1; /* the current sentinel. */
/* Count all the other sentinels. */
// 统计其他认为 master 进入下线状态的 Sentinel 的数量
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
// 该 SENTINEL 也认为 master 已下线
if (ri->flags & SRI_MASTER_DOWN) quorum++;
}
dictReleaseIterator(di);
// 如果投票得出的支持数目大于等于判断 ODOWN 所需的票数
// 那么进入 ODOWN 状态
if (quorum >= master->quorum) odown = 1;
}
/* Set the flag accordingly to the outcome. */
if (odown) {
// master 已 ODOWN
if ((master->flags & SRI_O_DOWN) == 0) {
// 发送事件
sentinelEvent(REDIS_WARNING,"+odown",master,"%@ #quorum %d/%d",
quorum, master->quorum);
// 打开 ODOWN 标志
master->flags |= SRI_O_DOWN;
// 记录进入 ODOWN 的时间
master->o_down_since_time = mstime();
}
} else {
// 未进入 ODOWN
if (master->flags & SRI_O_DOWN) {
// 如果 master 曾经进入过 ODOWN 状态,那么移除该状态
// 发送事件
sentinelEvent(REDIS_WARNING,"-odown",master,"%@");
// 移除 ODOWN 标志
master->flags &= ~SRI_O_DOWN;
}
}
}
逻辑比较简单,即就是看其他 sentinel 是否也认为该 master 主观下线,若超过 quorum 个 sentinel 认为其主观下线,则可以判定该 master 客观下线。
问题的关键是本 sentinel 怎么拿到其他 sentinel 对该 master 主观下线判断的信息,这个信息不是由上述的 3 个定时任务维护,而是由本 sentinel 主动询问。当本 sentinel 判断主观下线后,会向其他 sentinel 发送命令询问主观下线的情况,发送询问命令时也支持投票选举 header sentinel,但单纯询问阶段是不需要附带投票的,只有确定客观下线后,再次发送该命令时才会附带投票。
void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
dictIterator *di;
dictEntry *de;
// 遍历正在监视相同 master 的所有 sentinel
// 向它们发送 SENTINEL is-master-down-by-addr 命令
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
/* Ask */
// 发送 SENTINEL is-master-down-by-addr 命令
ll2string(port,sizeof(port),master->addr->port);
retval = redisAsyncCommand(ri->cc,
sentinelReceiveIsMasterDownReply, NULL,
"SENTINEL is-master-down-by-addr %s %s %llu %s",
master->addr->ip, port,
sentinel.current_epoch,
// 如果本 Sentinel 已经检测到 master 进入 ODOWN
// 并且要开始一次故障转移,那么向其他 Sentinel 发送自己的运行 ID
// 让对方将给自己投一票(如果对方在这个纪元内还没有投票的话)
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
server.runid : "*");
if (retval == REDIS_OK) ri->pending_commands++;
}
dictReleaseIterator(di);
}
回调函数 sentinelReceiveIsMasterDownReply 的处理。
void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) {
/* Ignore every error or unexpected reply.
* 忽略错误回复
* Note that if the command returns an error for any reason we'll
* end clearing the SRI_MASTER_DOWN flag for timeout anyway. */
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();
// 设置 SENTINEL 认为主服务器的状态
if (r->element[0]->integer == 1) {
// 已下线
ri->flags |= SRI_MASTER_DOWN;
} else {
// 未下线
ri->flags &= ~SRI_MASTER_DOWN;
}
// 如果运行 ID 不是 "*" 的话,那么这是一个带投票的回复
if (strcmp(r->element[1]->str,"*")) {
/* If the runid in the reply is not "*" the Sentinel actually
* replied with a vote. */
sdsfree(ri->leader);
// 打印日志
if (ri->leader_epoch != r->element[2]->integer)
redisLog(REDIS_WARNING,
"%s voted for %s %llu", ri->name,
r->element[1]->str,
(unsigned long long) r->element[2]->integer);
// 设置实例的领头
ri->leader = sdsnew(r->element[1]->str);
ri->leader_epoch = r->element[2]->integer;
}
}
}
故障转移准备
客观下线后,开始准备故障转移,需要先投票选举出执行故障转移的 header sentinel。
/****这里是核心逻辑代码中的一部分,用于投票,选出执行故障转移的header sentinel*****/
// sentinelStartFailoverIfNeeded的作用:
// 1、设置master->failover_state = SENTINEL_FAILOVER_STATE_WAIT_START
// 2、设置master->flags |= SRI_FAILOVER_IN_PROGRESS
// 3、设置master->failover_epoch = ++sentinel.current_epoch;
// 4、设置准入,如果当前master已经处于SRI_FAILOVER_IN_PROGRESS,则不再需要投票。避免下一周期
// 执行时又进行一轮投票
if (sentinelStartFailoverIfNeeded(ri))
// 强制向其他 Sentinel 发送 SENTINEL is-master-down-by-addr 命令,进行投票
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
哨兵集群的纪元是统一的,在两个时机哨兵会对纪元进行更新,以保持一致:
1、接收到 hello 消息,发现其中纪元比自身要大,则更新为消息中的纪元
2、投票时,发现请求中的纪元比自身要大,则更新为消息中的纪元
此处的新增纪元并不会触发其他哨兵更新纪元,否则很可能导致各个哨兵自己有自己的纪元,分别拉票。
投票选举
故障转移过程是以状态机的形式实现的,我们也按状态进行分析。这个状态下要干的事情是根据选票选出一个 header sentinel。
void sentinelFailoverWaitStart(sentinelRedisInstance *ri) {
char *leader;
int isleader;
/* Check if we are the leader for the failover epoch. */
// 获取给定纪元的领头 Sentinel
leader = sentinelGetLeader(ri, ri->failover_epoch);
// 本 Sentinel 是否为领头 Sentinel ?
isleader = leader && strcasecmp(leader,server.runid) == 0;
sdsfree(leader);
/* If I'm not the leader, and it is not a forced failover via
* SENTINEL FAILOVER, then I can't continue with the failover. */
if (!isleader && !(ri->flags & SRI_FORCE_FAILOVER)) {
int election_timeout = SENTINEL_ELECTION_TIMEOUT;
/* The election timeout is the MIN between SENTINEL_ELECTION_TIMEOUT
* and the configured failover timeout. */
// 当选的时长(类似于任期)是 SENTINEL_ELECTION_TIMEOUT
// 和 Sentinel 设置的故障迁移时长之间的较小那个值
if (election_timeout > ri->failover_timeout)
election_timeout = ri->failover_timeout;
// 没有选举为header的sentinel在之后的若干个执行周期都会走到这里,实际不干任何事情,直到选举
// 超时时间达到,会清空掉本轮故障转移的相关状态,此时有几种可能:
// 1、仍然没有选举出header,则再开始下一轮选举
// 2、上一轮故障转移未结束,则开始下一轮故障转移,开始选举
// 3、上一轮故障转移结束,测试故障已经转移,不会再开始选举
if (mstime() - ri->failover_start_time > election_timeout) {
sentinelEvent(REDIS_WARNING,"-failover-abort-not-elected",ri,"%@");
sentinelAbortFailover(ri);
}
return;
}
// 本 Sentinel 作为领头,开始执行故障迁移操作...
sentinelEvent(REDIS_WARNING,"+elected-leader",ri,"%@");
// 进入选择从服务器状态
ri->failover_state = SENTINEL_FAILOVER_STATE_SELECT_SLAVE;
ri->failover_state_change_time = mstime();
sentinelEvent(REDIS_WARNING,"+failover-state-select-slave",ri,"%@");
}
选择新 master
根据一些规则从 slaves 中选择一个最新的出来作为新 master,如果没有,则终止故障转移。
sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
sentinelRedisInstance **instance =
zmalloc(sizeof(instance[0])*dictSize(master->slaves));
sentinelRedisInstance *selected = NULL;
int instances = 0;
dictIterator *di;
dictEntry *de;
mstime_t max_master_down_time = 0;
// 计算可以接收的,从服务器与主服务器之间的最大下线时间
// 这个值可以保证被选中的从服务器的数据库不会太旧
if (master->flags & SRI_S_DOWN)
max_master_down_time += mstime() - master->s_down_since_time;
max_master_down_time += master->down_after_period * 10;
// 遍历所有从服务器
di = dictGetIterator(master->slaves);
while((de = dictNext(di)) != NULL) {
// 从服务器实例
sentinelRedisInstance *slave = dictGetVal(de);
mstime_t info_validity_time;
// 忽略所有 SDOWN 、ODOWN 或者已断线的从服务器
if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED)) continue;
if (mstime() - slave->last_avail_time > SENTINEL_PING_PERIOD*5) continue;
if (slave->slave_priority == 0) continue;
/* If the master is in SDOWN state we get INFO for slaves every second.
* Otherwise we get it with the usual period so we need to account for
* a larger delay. */
// 如果主服务器处于 SDOWN 状态,那么 Sentinel 以每秒一次的频率向从服务器发送 INFO 命令
// 否则以平常频率向从服务器发送 INFO 命令
// 这里要检查 INFO 命令的返回值是否合法,检查的时间会乘以一个倍数,以计算延迟
if (master->flags & SRI_S_DOWN)
info_validity_time = SENTINEL_PING_PERIOD*5;
else
info_validity_time = SENTINEL_INFO_PERIOD*3;
// INFO 回复已过期,不考虑
if (mstime() - slave->info_refresh > info_validity_time) continue;
// 从服务器下线的时间过长,不考虑
if (slave->master_link_down_time > max_master_down_time) continue;
// 将被选中的 slave 保存到数组中
instance[instances++] = slave;
}
dictReleaseIterator(di);
if (instances) {
// 对被选中的从服务器进行排序
qsort(instance,instances,sizeof(sentinelRedisInstance*),
compareSlavesForPromotion);
// 分值最低的从服务器为被选中服务器
selected = instance[0];
}
zfree(instance);
// 返回被选中的从服务区
return selected;
}
后续步骤
后续比较简单,就不一一说明了。完整的状态机如下。
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
redisAssert(ri->flags & SRI_MASTER);
// master 未进入故障转移状态,直接返回
if (!(ri->flags & SRI_FAILOVER_IN_PROGRESS)) return;
switch(ri->failover_state) {
// 等待故障转移开始
case SENTINEL_FAILOVER_STATE_WAIT_START:
sentinelFailoverWaitStart(ri);
break;
// 选择新主服务器
case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
sentinelFailoverSelectSlave(ri);
break;
// 升级被选中的从服务器为新主服务器
case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
sentinelFailoverSendSlaveOfNoOne(ri);
break;
// 等待升级生效,如果升级超时,那么重新选择新主服务器
// 具体情况请看 sentinelRefreshInstanceInfo 函数
case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION:
sentinelFailoverWaitPromotion(ri);
break;
// 向从服务器发送 SLAVEOF 命令,让它们同步新主服务器
case SENTINEL_FAILOVER_STATE_RECONF_SLAVES:
sentinelFailoverReconfNextSlave(ri);
break;
}
}
旺仔大菜包
还未添加个人签名 2020.04.30 加入
还未添加个人简介
评论