这篇文章主要用来说述redis高可用的实现原理,学习redis高可用实现,可让咱们更好的进行系统设计。也能够学习redis中的设计,将其迁移到其余系统的实现中去。redis
1.每10s,sentinel会发送info给全部data节点。算法
2.每2s,向_sentinel_:hello频道发送主节点判断以及当前sentinel节点的信息。服务器
<Sentinel节点IP> <Sentinel节点端口> <Sentinel节点runId> <Sentinel节点配置版本> <主节点名字> <主节点Ip> <主节点端口> <主节点配置版本>
复制代码
3.每1s,向其余节点发送ping(alive check)markdown
经过1和2,在sentinel启动的时候只须要配置要监控的master,便可实现对其余slave和sentinel的自动发现机制。主要依赖对redis data节点的发布订阅和info操做去作这个事情。网络
主观上判断某个节点下线(在超时时间没有ping经过)数据结构
若是主观下线的是主节点,则会经过sentinel is- master-down-by-addr命令向其余sentinel节点询问对主节点的判断。超高quorum数量,会进行客观下线。架构
全部sentinel节点除了定时pub hello消息,还经过这个命令进行p2p通讯。及时告知其余sentinel信息。 该命令做用:app
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
复制代码
若是runid为*表明主节点下线命令运维
若是不为*表明请求投票命令异步
主节点下线命令
sentinel is-master-down-by-addr 127.0.0.1 6379 0 *
复制代码
领导选举投票 使用raft的领主选举算法,经过该命令投票选举
命令返回结果
主节点故障,选举从节点 1.选哪一个?
(1)过滤不健康的
(2)选slave-priority最高的从节点列表
(3)选择复制偏移量最大的。
(4)选择runid最小的
2.执行slaveof no one
3.向剩余从节点发送命令,让其成为新master的从
4.将原来的主更新为从。并保持对其关注
图中每一个粉色的方框都表明一个SentinelRedisInstance,这是sentienl中统一的一个数据结构,简单来说就是一块内存,里面存储了节点的信息。每个redis data节点或者sentinel节点都会有一个SentinelRedisInstance(除了当前sentienl)。就比如每一条网络链接都有一个socket同样。
master表明监控的master信息。每一个sentinel有多个master(上面只画了一个),表明能够同时监控多个master节点。
SentinelRedisInstance结构有两个hash:key为名字,val为SentinelRedisInstance。
一个用来存储当前master的slave,另外一个用来存储一样监控这个master的其余sentinel节点(如上图结构),使用hash 是为了经过key进行快速查找对应结构。
// 其余一样监控这个主服务器的全部 sentinel
dict *sentinels; /* Other sentinels monitoring the same master. */
// 若是这个实例表明的是一个主服务器
// 那么这个字典保存着主服务器属下的从服务器
// 字典的键是从服务器的名字,字典的值是从服务器对应的 sentinelRedisInstance 结构
dict *slaves; /* Slaves for this master instance. */
复制代码
固然只有SentinelRedisInstance为master的时候,这两个hash才会被用到。因此sentinel的全部操做都围绕这个master结构进行。sentinel经过info命令以及publish hello消息发现其余的slave和sentinel,新加入的节点会加入对应hash中。
flags
惟一须要强调的是SentinelRedisInstance中的flags变量。该变量记录了当前节点的状态。(flags经过标志位记录多种状态,每一个状态一个位,经过|~进行更新)
因此sentinel彻底围绕这些SentinelRedisInstance去维护整个集群的逻辑。好比ping,info,就是遍历SentinelRedisInstance,对其hostport进行ping,info等操做。
遍历逻辑:先拿到master的SentinelRedisInstance,执行逻辑,而后再遍历两个hash处理。
sentinel 模式启动会执行下面逻辑
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
复制代码
initSentinel主要就是一些初始化工做,包括sentinel结构建立、一些全局变量的初始化、以及注册命令处理器。
服务启动会先加载配置。这个方法主要是sentinel配置的解析逻辑。
if (!strcasecmp(argv[0],"monitor") && argc == 5) {
/* monitor <name> <host> <port> <quorum> */
// 读入 quorum 参数
int quorum = atoi(argv[4]);
// 检查 quorum 参数必须大于 0
if (quorum <= 0) return "Quorum must be 1 or greater.";
// 建立主服务器实例
if (createSentinelRedisInstance(argv[1],SRI_MASTER,argv[2],
atoi(argv[3]),quorum,NULL) == NULL)
{
switch(errno) {
case EBUSY: return "Duplicated master name.";
case ENOENT: return "Can't resolve master instance hostname.";
case EINVAL: return "Invalid port number";
}
}
}else if (!strcasecmp(argv[0],"down-after-milliseconds") && argc == 3) {
/* down-after-milliseconds <name> <milliseconds> */
// 查找主服务器
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
// 设置选项
ri->down_after_period = atoi(argv[2]);
if (ri->down_after_period <= 0)
return "negative or zero time parameter.";
sentinelPropagateDownAfterPeriod(ri);
}
复制代码
核心就是对master的SentinelRedisInstance结构建立。而后将其余配置初始化到该结构中。上面只列举了部分代码。其余配置相似down-after-milliseconds的解析逻辑。
用来建立sentinel中实例。包括sentinel、被监控的redis的主从。每次有新增都会调用该方法建立一个实例,并加入到master的SentinelRedisInstance中(除了master自身)
上面基本就是一些初始化工做。sentinel核心逻辑是须要时间函数驱动的,因此咱们直接看sentinelTimer逻辑。
该函数100ms执行一次。
void sentinelTimer(void) {
// 记录本次 sentinel 调用的事件,判断是否须要进入 TITL 模式
sentinelCheckTiltCondition();
// 执行按期操做
// 好比 PING 实例、分析主服务器和从服务器的 INFO 命令
// 向其余监视相同主服务器的 sentinel 发送问候信息
// 并接收其余 sentinel 发来的问候信息
// 执行故障转移操做,等等
sentinelHandleDictOfRedisInstances(sentinel.masters);
// 运行等待执行的脚本
sentinelRunPendingScripts();
// 清理已执行完毕的脚本,并重试出错的脚本
sentinelCollectTerminatedScripts();
// 杀死运行超时的脚本
sentinelKillTimedoutScripts();
/* We continuously change the frequency of the Redis "timer interrupt" * in order to desynchronize every Sentinel from every other. * This non-determinism avoids that Sentinels started at the same time * exactly continue to stay synchronized asking to be voted at the * same time again and again (resulting in nobody likely winning the * election because of split brain voting). */
server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}
复制代码
sentinelCheckTiltCondition方法用来判断是否进入ttl,而且记录执行时间。
TITL模式:由于sentinel依赖本机时间驱动,若是系统时间出问题,或者由于进程阻塞致使的时间函数延迟调用。这时再去参与集群逻辑会出现不正确的决策。所以若是当前时间和上一次执行时间差为负值或者超过2s,该节点会进入TILT模式。
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);
// 若是被遍历的是主服务器,那么递归地遍历该主服务器的全部从服务器
if (ri->flags & SRI_MASTER) {
// 全部从服务器
sentinelHandleDictOfRedisInstances(ri->slaves);
// 全部 sentinel
sentinelHandleDictOfRedisInstances(ri->sentinels);
if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {
// 已选出新的主服务器
switch_to_promoted = ri;
}
}
}
// 将原主服务器(已下线)从主服务器表格中移除,并使用新主服务器代替它
if (switch_to_promoted)
sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);
dictReleaseIterator(di);
}
复制代码
这个方法就递归遍历全部SentinelRedisInstance。。核心就是对被 Sentinel 监视的全部实例(包括主服务器、从服务器和其余 Sentinel ) 进行按期操做。
逻辑很简单,其实就是执行sentinelHandleRedisInstance方法。
若是有故障转移(从节点升级为主节点),则调用sentinelFailoverSwitchToPromotedSlave替换新主服务。
Sentinel的主逻辑流程,后面会一一介绍每一个方法的逻辑。该方法按期调用,非阻塞(中间的io命令都会异步出去)。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
/* Every kind of instance */
// 若是有须要的话,建立连向实例的网络链接
sentinelReconnectInstance(ri);
// 根据状况,向实例发送 PING、 INFO 或者 PUBLISH 命令
sentinelSendPeriodicCommands(ri);
/* ============== ACTING HALF ============= */
/* We don't proceed with the acting half if we are in TILT mode. * TILT happens when we find something odd with the time, like a * sudden change in the clock. */
if (sentinel.tilt) {
// 若是 TILI 模式未解除,那么不执行动做
if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
// 时间已过,退出 TILT 模式
sentinel.tilt = 0;
sentinelEvent(REDIS_WARNING,"-tilt",NULL,"#tilt mode exited");
}
/* 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 is-master-down-by-addr 命令
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
}
复制代码
1.sentinelReconnectInstance主要就是建立和实例的链接。若是是master或者slave实例,则订阅对应节点的__sentinel__:hello频道,用来接受其余sentinel广播的消息。每一个sentinel会按期给该频道publish。 由于启动的时候只是初始化了实例数据,并无建立链接。对于每个实例,都是在这里去建立链接的。
2.sentinelSendPeriodicCommands主要发送ping、info、publish命令。
3.若是当前在tilt模式中,直接返回,非tilt模式才会执行后续操做。
4.sentinelCheckSubjectivelyDown,检查给定实例是否进入 SDOWN 状态
5.若是当前实例为主服务器,则执行一些故障判断和故障转移操做。
能够看出来sentienl的全部逻辑都是围绕一个主服务进行的。
这是咱们的核心关注点,sentinel之间的通讯逻辑。
这个方法主要是ping、info以及publish hello消息。到达执行时刻会执行对应逻辑。
redis经过pending_commands记录当前正在异步执行的命令。若是超过100,则再也不发送,避免命令堆积。由于sentinel是依赖上一次响应时间来判断是否发送命令,若是出现网络阻塞或者波动,会致使频繁发送。
发送逻辑在sentinelSendPeriodicCommands方法中。
Ping命令回调方法sentinelPingReplyCallback:,若是回调正常,更新对应字段(last_avail_time、last_pong_time),sentienl依赖这些字段进行判活。若是节点执行lua超时,则调用SCRIPT KILL尝试杀死脚本。
info为获取redis节点信息的命令。处理返回结果的方法为sentinelRefreshInstanceInfo
sentinelRefreshInstanceInfo方法主要是解析并提取须要的数据信息。下面介绍一些核心信息。
1.若是是主节点,提取其从节点的hostport,并为从节点建立实例信息(若是是新发现的),sentinel依赖此方式发现其余的slave节点。
if (sentinelRedisInstanceLookupSlave(ri,ip,atoi(port)) == NULL) {
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,ip,
atoi(port), ri->quorum, ri)) != NULL)
{
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
}
}
复制代码
2.若是是从节点,记录当前从服务器对应主节点的信息。由于sentinel的记录的主节点不必定是正确的(网络分区致使切换延迟),因此经过info获取到该从服务器最新信息。以供后面逻辑处理。
这里其实两种主(从)节点状态:一种是sentienl认为当前节点为主(从)节点,另外一种是当前节点认为的主(从)节点。
3.若是发生了角色转变(info返回的和当前sentinel记录的节点状态不一致),更新转变时间。若是tilt,直接返回。不然,根据该节点返回的role和sentinel记录的role进行一些逻辑,具体逻辑咱们后面再研究。
给对应channel发送的信息以下,主要包括对应sentinel的信息和当前master的信息
snprintf(payload,sizeof(payload),
"%s,%d,%s,%llu," /* Info about this sentinel. */
"%s,%s,%d,%llu", /* Info about current master. */
ip, server.port, server.runid,
(unsigned long long) sentinel.current_epoch,
/* --- */
master->name,master_addr->ip,master_addr->port,
(unsigned long long) master->config_epoch);
复制代码
该方法为sentinel处理hello信息的方法。
1.若是hello消息中sentinel节点为新发现的节点(当前setinel不存在的),为新节点建立实例,加入到列表中。这是sentinel发现其余sentinel的惟一方式。
2.更新current_epoch=max(当前current_epoch,hello的current_epoch)
3.若是hello消息的master_config_epoch比该节点master的config_epoch大。则调用sentinelResetMasterAndChangeAddress方法切换当前master。config_epoch为故障转移使用的纪元。故障转移以后会递增。若是发现比较大的,说明进行了故障转移,则信任hello中的master为最新master
if (master->config_epoch < master_config_epoch) {
master->config_epoch = master_config_epoch;
if (master_port != master->addr->port ||
strcmp(master->addr->ip, token[5]))
{
sentinelAddr *old_addr;
sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");
sentinelEvent(REDIS_WARNING,"+switch-master",
master,"%s %s %d %s %d",
master->name,
master->addr->ip, master->addr->port,
token[5], master_port);
old_addr = dupSentinelAddr(master->addr);
sentinelResetMasterAndChangeAddress(master, token[5], master_port);
sentinelCallClientReconfScript(master,
SENTINEL_OBSERVER,"start",
old_addr,master->addr);
releaseSentinelAddr(old_addr);
}
}
复制代码
检查实例是否进入 SDOWN 状态
1.若是实例符合断线重连的条件,则断开该实例链接,等待下次从新链接。其实就是对不活跃实例进行断线重连。
2.设置或者取消SDOWN标志
达到下面两个条件设置为SDOWN,不然取消SDOWN标志位
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 is-master-down-by-addr 命令
// 刷新其余 Sentinel 关于主服务器的状态
// 这一句是对那些没有进入 if(sentinelStartFailoverIfNeeded(ri)) { /* ... */ }
// 语句的主服务器使用的
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
复制代码
判断当前主节点是否进入ODown状态。
经过遍历全部sentinel实例的flags标志位进行判断。若是一半以上主观下线,则变动为客观下线。这个状态位是在is-master-down-by-addr命令回调中更新的。
判断是否须要进行故障转移
void sentinelStartFailover(sentinelRedisInstance *master) {
redisAssert(master->flags & SRI_MASTER);
// 更新故障转移状态
master->failover_state = SENTINEL_FAILOVER_STATE_WAIT_START;
// 更新主服务器状态
master->flags |= SRI_FAILOVER_IN_PROGRESS;
// 更新纪元
master->failover_epoch = ++sentinel.current_epoch;
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
(unsigned long long) sentinel.current_epoch);
sentinelEvent(REDIS_WARNING,"+try-failover",master,"%@");
// 记录故障转移状态的变动时间
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
master->failover_state_change_time = mstime();
}
复制代码
判断条件故障转移:
1.进入ODOWN而且没有在故障转移中。
2.若是发现故障转移过于频繁也不执行。
若是须要故障转移,则更新当前master的信息,主要是failover_state、failover_epoch等字段。
failover_epoch为当前master纪元+1。
failover_epoch做用:
- 其余sentinel依赖这个字段判断是否须要进行故障转移。这个在以前的hello中有说到。
- 当前sentinel依赖这个纪元选出执行故障转移的leader。由于选举使用的也是该纪元。选举出来的leader的纪元应该一致。每次选举都会产生一个新的leader,最新的纪元最权威,这是raft领导选举的核心概念。虽然raft使用的是term。
failover_state表明当前故障转移状态。故障转移操做须要依赖该状态。
故障转移操做须要从sentienl中选举一个执行。因此这只是先更新状态。
向其余sentine询问master状态。
这会遍历全部sentinel,若是当前sentinel认为master下线,而且链接正常,会发送is-master-down-by-addr命令
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
复制代码
若是本sentinel检测到master 主观下线(经过failover_state判断),则runid为当前server的runid,表明让其余sentienl给本身投票。
若是是master客观下线,则runid=*,表明告诉其余sentienl,主节点下线。这是sentinel通知其余sentinel主节点下线的惟一方式。
该逻辑在sentinelCommand中执行
ri = getSentinelRedisInstanceByAddrAndRunID(sentinel.masters,
c->argv[2]->ptr,port,NULL);
if (!sentinel.tilt && ri && (ri->flags & SRI_S_DOWN) &&
(ri->flags & SRI_MASTER))
isdown = 1;
/* Vote for the master (or fetch the previous vote) if the request * includes a runid, otherwise the sender is not seeking for a vote. */
if (ri && ri->flags & SRI_MASTER && strcasecmp(c->argv[5]->ptr,"*")) {
leader = sentinelVoteLeader(ri,(uint64_t)req_epoch,
c->argv[5]->ptr,
&leader_epoch);
}
/* Reply with a three-elements multi-bulk reply: * down state, leader, vote epoch. */
// 多条回复
// 1) <down_state> 1 表明下线, 0 表明未下线
// 2) <leader_runid> Sentinel 选举做为领头 Sentinel 的运行 ID
// 3) <leader_epoch> 领头 Sentinel 目前的配置纪元
addReplyMultiBulkLen(c,3);
addReply(c, isdown ? shared.cone : shared.czero);
addReplyBulkCString(c, leader ? leader : "*");
addReplyLongLong(c, (long long)leader_epoch);
复制代码
若是节点下线回复down_state1,不然为0
根据ip和port获取ri(sentinelRedisInstance),若是ri是主节点,而且runid不为*,则进行选举投票。
投票逻辑:
请求的req_epoch>当前sentinel的current_epoch(更新sentinel的current_epoch)
req_epoch>master的master.leader_epoch,而且>=sentinel.current_epoch,更新master的leader为req_runid。而后投票给当前req_runid节点。
其实就是判断epoch。若是请求的epoch比较大,那就投票便可。和raft的领导选举同样。
// 更新最后一次回复询问的时间
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;
}
复制代码
1.更新回复时间
2.更新被询问的sentinel的flag。用于ODOWN判断。
3.若是leader_runid非*,表明投票信息,更新该sentinel的leader信息。
ri的leader字段有两个状态:
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;
}
复制代码
整个流程如上:
1.sentinelFailoverWaitStart会根据当前全部sentinel的leader以及当前故障转移纪元选出来leader。若是发现leader是本身,则切换failover_state为SENTINEL_FAILOVER_STATE_SELECT_SLAVE,执行下一个case。不然跳出。
2.根据逻辑选举从服务器做为新的主节点,若是没有选出,清空故障转移状态。选出成功后继续更新状态执行下一个case。选择逻辑上面有说过。
3.升级新节点为主节点,若是发现断线并超时,则终止故障转移逻辑。这个其实就是异步发送slave of命令。让从节点升级为主节点。这个命令在redis复制源码解析中说过。
4.SENTINEL_FAILOVER_STATE_WAIT_PROMOTION状态只是等待升级的另外一个逻辑,若是升级超时则终止故障转移。 如何检测从节点升级主节点成功?
其实在info中有一段逻辑:
if ((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
(ri->master->failover_state ==
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION))
{
// 更新从服务器的主服务器(已下线)的配置纪元
ri->master->config_epoch = ri->master->failover_epoch;
// 设置从服务器的主服务器(已下线)的故障转移状态
// 这个状态会让从服务器开始同步新的主服务器
ri->master->failover_state = SENTINEL_FAILOVER_STATE_RECONF_SLAVES;
// 更新从服务器的主服务器(已下线)的故障转移状态变动时间
ri->master->failover_state_change_time = mstime();
// 将当前 Sentinel 状态保存到配置文件里面
sentinelFlushConfig();
// 发送事件
sentinelEvent(REDIS_WARNING,"+promoted-slave",ri,"%@");
sentinelEvent(REDIS_WARNING,"+failover-state-reconf-slaves",
ri->master,"%@");
// 执行脚本
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
"start",ri->master->addr,ri->addr);
}
复制代码
在这里表明当前从节点已经升级成功,则更新config_epoch。config_epoch其实在hello消息中被用到。若是其余sentienl发现它的config_epoch小于hello消息中的config_epoch,则会重置master的地址。
5.SENTINEL_FAILOVER_STATE_RECONF_SLAVES
Info更新成功后,会执行sentinelFailoverReconfNextSlave方法。其实就是向全部从服务器发送slave of命令。 这里会受parallel_syncs参数限制。控制并行slave of数量,避免主节点网络压力。
成功以后更新failover_state状态为SENTINEL_FAILOVER_STATE_UPDATE_CONFIG
最终会调用该方法切换主服务器,整个转移过程结束。
若是执行故障转移的leader存活的状况,转移超时,则会调用sentinelAbortFailover方法终止故障转移。 若是转移过场中leader宕机,其余节点会继续执行故障转移逻辑。
down-after-milliseconds 节点alive check的ttl
sentinel parallel-syncs 主节点宕机,容许并行复制数量
sentinel failover-timeout 故障转移的超时时间,若是当前时间-上次转移状态更新时间大于该值,则会终止转移。
sentinel notification-script 故障监控,sentinel警告级别的事件发生,会出发对应路径下的脚本,经过脚本可接收参数。进行监控。
启动经过遍历sentinel获取redis主节点。而后订阅每一个sentinel的switch事件,保证在主备切换的时候能监听到。若是监听到变化 从新初始化链接池便可。
须要注意的是,只有故障转移完成才会发送此事件。
整个实现比较复杂,可是按照每一个点仍是能够理清楚。经过ping、info、以及pubsub hello消息实现通讯。保证整个系统的一致性。每次故障转移只会由一个sentinel执行,这个选举过程依赖raft算法leader选举逻辑。故障转移逻辑依赖超时时间避免死状态。整个逻辑依赖状态机进行切换,有条不紊,值得借鉴。