intserverCron(struct aeEventLoop *eventLoop, longlong id, void *clientData){ ······ /* Run the Sentinel timer if we are in sentinel mode. */ if (server.sentinel_mode) sentinelTimer(); ······ }
/* This function checks if we need to enter the TILT mode. * * 由于 redis sentinel 机制极度依赖系统时间,所以 redis 提供了 * TILT 模式,目的是当 redis 探测到系统时间发生异常时 *(比如上次 timer 调用的时间比当前时间晚,或者早 2s 以上) * 自动进入 protection mode。 * 在进入 TILE mode 之后, * 1. sentinel 的监控功能正常运行,除此之外不再做其他事情 * 2. 对 SENTINEL is-master-down-by-addr 请求答复 负数, * 表示自己处于异常状态,自己的应答不可信 * 当时间异常恢复 30s 之后,就会退出 TILT 模式 * * 其他可以引起时间异常的情况: * 1) The Sentinel process for some time is blocked, for every kind of * random reason: the load is huge, the computer was frozen for some time * in I/O or alike, the process was stopped by a signal. Everything. * 2) The system clock was altered significantly. * * During TILT time we still collect information, we just do not act. */ voidsentinelCheckTiltCondition(void){ mstime_t now = mstime(); mstime_t delta = now - sentinel.previous_time;
/* Main state. */ structsentinelState { char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */ uint64_t current_epoch; /* Current epoch. */ dict *masters; /* Dictionary of master sentinelRedisInstances. Key is the instance name, value is the sentinelRedisInstance structure pointer. */ int tilt; /* Are we in TILT mode? */ mstime_t tilt_start_time; /* TITL 模式开始的时间. */ mstime_t previous_time; /* 上一次 sentinel timer 运行的时间,用来判断 TITL 模式 */ list *scripts_queue; /* Queue of user scripts to execute. */ char *announce_ip; /* IP addr that is gossiped to other sentinels if not NULL. */ int announce_port; /* Port that is gossiped to other sentinels if non zero. */ ······ } sentinel;
typedefstructsentinelRedisInstance { int flags; /* See SRI_... defines */ char *name; /* Master name from the point of view of this sentinel. */ char *runid; /* Run ID of this instance, or unique ID if is a Sentinel.*/ uint64_t config_epoch; /* Configuration epoch. */ sentinelAddr *addr; /* Master host. */ instanceLink *link; /* Link to the instance, may be shared for Sentinels. */ mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */ mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time we received a hello from this Sentinel via Pub/Sub. */ mstime_t last_master_down_reply_time; /* Time of last reply to SENTINEL is-master-down command. */ mstime_t s_down_since_time; /* Subjectively down since time. */ mstime_t o_down_since_time; /* Objectively down since time. */ mstime_t down_after_period; /* Consider it down after that period. */ mstime_t master_reboot_down_after_period; /* Consider master down after that period. */ mstime_t master_reboot_since_time; /* master reboot time since time. */ mstime_t info_refresh; /* Time at which we received INFO output from it. */ dict *renamed_commands; /* Commands renamed in this instance: Sentinel will use the alternative commands mapped on this table to send things like SLAVEOF, CONFING, INFO, ... */
/* Role and the first time we observed it. * This is useful in order to delay replacing what the instance reports * with our own configuration. We need to always wait some time in order * to give a chance to the leader to report the new configuration before * we do silly things. */ int role_reported; mstime_t role_reported_time; mstime_t slave_conf_change_time; /* Last time slave master addr changed. */
/* Master specific. */ dict *sentinels; /* Other sentinels monitoring the same master. */ dict *slaves; /* Slaves for this master instance. */ unsignedint quorum;/* Number of sentinels that need to agree on failure. */ int parallel_syncs; /* How many slaves to reconfigure at same time. */ char *auth_pass; /* Password to use for AUTH against master & replica. */ char *auth_user; /* Username for ACLs AUTH against master & replica. */
/* Slave specific. */ mstime_t master_link_down_time; /* Slave replication link down time. */ int slave_priority; /* Slave priority according to its INFO output. */ int replica_announced; /* Replica announcing according to its INFO output. */ mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */ structsentinelRedisInstance *master; /* Master instance if it's slave. */ char *slave_master_host; /* Master host as reported by INFO */ int slave_master_port; /* Master port as reported by INFO */ int slave_master_link_status; /* Master link status as reported by INFO */ unsignedlonglong slave_repl_offset; /* Slave replication offset. */ /* Failover */ 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. */ int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */ mstime_t failover_state_change_time; mstime_t failover_start_time; /* Last failover attempt start time. */ mstime_t failover_timeout; /* Max time to refresh failover state. */ mstime_t failover_delay_logged; /* For what failover_start_time value we logged the failover delay. */ structsentinelRedisInstance *promoted_slave; /* Promoted slave instance. */ } sentinelRedisInstance;
我们注意这两个字段:
1 2
dict *sentinels; /* Other sentinels monitoring the same master. */ dict *slaves; /* Slaves for this master instance. */
/* Perform scheduled operations for all the instances in the dictionary. * Recursively call the function against dictionaries of slaves. */ voidsentinelHandleDictOfRedisInstances(dict *instances){ dictIterator *di; dictEntry *de; sentinelRedisInstance *switch_to_promoted = NULL;
/* Send periodic PING, INFO, and PUBLISH to the Hello channel to * the specified master or slave instance. */ voidsentinelSendPeriodicCommands(sentinelRedisInstance *ri){ mstime_t now = mstime(); mstime_t info_period, ping_period; int retval; ······
intsentinelSendPing(sentinelRedisInstance *ri){ int retval = redisAsyncCommand(ri->link->cc, sentinelPingReplyCallback, ri, "%s", sentinelInstanceMapCommand(ri,"PING")); if (retval == C_OK) { ri->link->pending_commands++; ri->link->last_ping_time = mstime(); /* We update the active ping time only if we received the pong for * the previous ping, otherwise we are technically waiting since the * first ping that did not receive a reply. */ if (ri->link->act_ping_time == 0) ri->link->act_ping_time = ri->link->last_ping_time; return1; } else { return0; } }
b. info 命令
既然向其他实例发送了 info 命令,就会有对应的 handler 来处理,也就是上面代码中的sentinelInfoReplyCallback。哨兵拿到了 info 命令返回的实例的信息之后,最重要的是刷新一下本地的信息,也就是上面提到的 sentinel redis instance 字典,这里更新的信息,为主观下线等后续步骤提供了依据:
/* Process the INFO output from masters. */ voidsentinelRefreshInstanceInfo(sentinelRedisInstance *ri, constchar *info){ ......
/* Process line by line. */ lines = sdssplitlen(info,strlen(info),"\r\n",2,&numlines); for (j = 0; j < numlines; j++) { sentinelRedisInstance *slave; sds l = lines[j];
/* ---------------------------- Acting half ----------------------------- * Some things will not happen if sentinel.tilt is true, but some will * still be processed. */
/* Remember new role and when the role changed. * (被别的 sentinel 切成了 master)*/ if (role != ri->role_reported) { ri->role_reported_time = mstime(); ri->role_reported = role; if (role == SRI_SLAVE) ri->slave_conf_change_time = mstime(); }
/* None of the following conditions are processed when in tilt mode, so * return asap. */ if (sentinel.tilt) return; ······
/* 处理 master -> slave 切换. */ if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE) { /* Nothing to do, but masters claiming to be slaves are * considered to be unreachable by Sentinel, so eventually * a failover will be triggered. */ }
/* Handle slaves replicating to a different master address. */ if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE && (ri->slave_master_port != ri->master->addr->port || !sentinelAddrEqualsHostname(ri->master->addr, ri->slave_master_host))) { mstime_t wait_time = ri->master->failover_timeout;
/* Make sure the master is sane before reconfiguring this instance * into a slave. */ if (sentinelMasterLooksSane(ri->master) && sentinelRedisInstanceNoDownFor(ri,wait_time) && mstime() - ri->slave_conf_change_time > wait_time) { int retval = sentinelSendSlaveOf(ri,ri->master->addr); if (retval == C_OK) sentinelEvent(LL_NOTICE,"+fix-slave-config",ri,"%@"); } }
/* Detect if the slave that is in the process of being reconfigured * changed state. */ if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE && (ri->flags & (SRI_RECONF_SENT|SRI_RECONF_INPROG))) { /* SRI_RECONF_SENT -> SRI_RECONF_INPROG. */ if ((ri->flags & SRI_RECONF_SENT) && ri->slave_master_host && sentinelAddrEqualsHostname(ri->master->promoted_slave->addr, ri->slave_master_host) && ri->slave_master_port == ri->master->promoted_slave->addr->port) { ri->flags &= ~SRI_RECONF_SENT; ri->flags |= SRI_RECONF_INPROG; sentinelEvent(LL_NOTICE,"+slave-reconf-inprog",ri,"%@"); }
/* Process a hello message received via Pub/Sub in master or slave instance, * or sent directly to this sentinel via the (fake) PUBLISH command of Sentinel. * * 如果 message 中没有 master name,这条信息就会被无视 */ voidsentinelProcessHelloMessage(char *hello, int hello_len){ /* Format is composed of 8 tokens: * 0=ip,1=port,2=runid,3=current_epoch,4=master_name, * 5=master_ip,6=master_port,7=master_config_epoch. */ int numtokens, port, removed, master_port; uint64_t current_epoch, master_config_epoch; char **token = sdssplitlen(hello, hello_len, ",", 1, &numtokens); sentinelRedisInstance *si, *master;
// 如果这 8 个 field 不齐全,直接退出 if (numtokens == 8) { /* 拿到 master */ master = sentinelGetMasterByName(token[4]); if (!master) goto cleanup; /* Unknown master, skip the message. */
/* 首先,判断我们是否已经知道这个 sentinel */ port = atoi(token[1]); master_port = atoi(token[6]); si = getSentinelRedisInstanceByAddrAndRunID( master->sentinels,token[0],port,token[2]); current_epoch = strtoull(token[3],NULL,10); master_config_epoch = strtoull(token[7],NULL,10);
if (!si) { /* 如果是一个新来的 sentinel(新的ip:port),就把他加入 master->sentinels 中 */ ······
/* Add the new sentinel. */ si = createSentinelRedisInstance(token[2],SRI_SENTINEL, token[0],port,master->quorum,master); ······ }
/* If the master state from other sentinel is too old, we clear it. */ if (elapsed > sentinel_ask_period*5) { ri->flags &= ~SRI_MASTER_DOWN; sdsfree(ri->leader); // 这里将会用来存储执行 failover 的 sentinel 的 runid ri->leader = NULL; }
// 如果只是主观下线,而不是客观下线,不切换 master if ((master->flags & SRI_S_DOWN) == 0) continue; // 如果某个 sentinel 连接异常,跳过不作处理 if (ri->link->disconnected) continue; ······
/* Scan all the Sentinels attached to this master to check if there * is a leader for the specified epoch. * * To be a leader for a given epoch, we should have the majority of * the Sentinels we know (ever seen since the last SENTINEL RESET) that * reported the same instance as leader for the same epoch. */ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch){ dict *counters; dictIterator *di; dictEntry *de; unsignedint voters = 0, voters_quorum; char *myvote; char *winner = NULL; uint64_t leader_epoch; uint64_t max_votes = 0;
/* Helper for sentinelSelectSlave(). This is used by qsort() in order to * sort suitable slaves in a "better first" order, to take the first of * the list. */ intcompareSlavesForPromotion(constvoid *a, constvoid *b){ sentinelRedisInstance **sa = (sentinelRedisInstance **)a, **sb = (sentinelRedisInstance **)b; char *sa_runid, *sb_runid;
// 配置文件里的优先级参数 if ((*sa)->slave_priority != (*sb)->slave_priority) return (*sa)->slave_priority - (*sb)->slave_priority;
/* 默认情况下,优先级都一样,这里选择数据更完整的 salve */ if ((*sa)->slave_repl_offset > (*sb)->slave_repl_offset) { return-1; /* a < b */ } elseif ((*sa)->slave_repl_offset < (*sb)->slave_repl_offset) { return1; /* a > b */ }
/* 数据都很完整,就比较 runid,runid 更小,说明存活的更久 * 说明可靠性更高 */ sa_runid = (*sa)->runid; sb_runid = (*sb)->runid; if (sa_runid == NULL && sb_runid == NULL) return0; elseif (sa_runid == NULL) return1; /* a > b */ elseif (sb_runid == NULL) return-1; /* a < b */ returnstrcasecmp(sa_runid, sb_runid); }
/* 发送 SLAVEOF NO ONE 把 slave 转换为 master. * 这里其实并不关心对方的回复,因为我们在后续 cron 中会通过 info 命令 * 拿到每个节点的信息 */ retval = sentinelSendSlaveOf(ri->promoted_slave,NULL); if (retval != C_OK) return; sentinelEvent(LL_NOTICE, "+failover-state-wait-promotion", ri->promoted_slave,"%@"); // ri->failover_state = SENTINEL_FAILOVER_STATE_WAIT_PROMOTION; ri->failover_state_change_time = mstime(); }
d. 等待 slave 升主完成
这一步只是单纯地等待,上文提到过 cron 中会定时发送 info 命令给各个节点,拿到每个节点的身份信息;除非等待超时,这时候就放弃此次故障转移。
1 2 3 4 5 6 7 8 9 10
/* We actually wait for promotion indirectly checking with INFO when the * slave turns into a master. */ voidsentinelFailoverWaitPromotion(sentinelRedisInstance *ri){ /* Just handle the timeout. Switching to the next state is handled * by the function parsing the INFO command of the promoted slave. */ if (mstime() - ri->failover_state_change_time > ri->failover_timeout) { sentinelEvent(LL_WARNING,"-failover-abort-slave-timeout",ri,"%@"); sentinelAbortFailover(ri); } }
if (slave->flags & (SRI_RECONF_SENT|SRI_RECONF_INPROG)) in_progress++; } dictReleaseIterator(di);
di = dictGetIterator(master->slaves); while(in_progress < master->parallel_syncs && (de = dictNext(di)) != NULL) { sentinelRedisInstance *slave = dictGetVal(de); int retval;
/* 跳过刚刚升主的 slave,和已经重新配置了的 slave */ if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE)) continue;
/* If too much time elapsed without the slave moving forward to * the next state, consider it reconfigured even if it is not. * Sentinels will detect the slave as misconfigured and fix its * configuration later. */ if ((slave->flags & SRI_RECONF_SENT) && (mstime() - slave->slave_reconf_sent_time) > sentinel_slave_reconf_timeout) { sentinelEvent(LL_NOTICE,"-slave-reconf-sent-timeout",slave,"%@"); slave->flags &= ~SRI_RECONF_SENT; slave->flags |= SRI_RECONF_DONE; }
/* 遇到已经发送过 slave of 命令的、正在同步数据的、断开连接的 slave, * 直接跳过 */ if (slave->flags & (SRI_RECONF_SENT|SRI_RECONF_INPROG)) continue; if (slave->link->disconnected) continue;
/* Run pending scripts if we are not already at max number of running * scripts. */ voidsentinelRunPendingScripts(void){ listNode *ln; listIter li; mstime_t now = mstime();
/* 只有运行成功的任务,才会被从队列中移除;执行失败的任务(脚本没能返回1), * 会被塞回队列中重新运行,直到运行成功或者达到 max retry time 为止. */ voidsentinelCollectTerminatedScripts(void){ int statloc; pid_t pid;
while ((pid = waitpid(-1, &statloc, WNOHANG)) > 0) { int exitcode = WEXITSTATUS(statloc); int bysignal = 0; listNode *ln; sentinelScriptJob *sj;