写点什么

Redis - 哨兵

发布于: 2021 年 06 月 14 日
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 加入

还未添加个人简介

评论

发布
暂无评论
Redis - 哨兵