简单的概念就不解释。基于Redis5.0.5node
在sentinel.c文件的最后面能够发现sentinelTimer函数,这个就是Sentinel的主函数,sentinel的各项功能检测都是在这里进行,循环调用。redis
void sentinelTimer(void) { // 并判断是否须要进入 TITL 模式 sentinelCheckTiltCondition(); // 执行按期操做 // 好比 PING 实例、分析主服务器和从服务器的 INFO 命令 // 向其余监视相同主服务器的 发送问候信息 // 并接收其余 发来的问候信息 // 执行故障转移操做,等等 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 = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ; }
Sentinel主函数(sentinelTimer)由server.c中的serverCron()函数调用,serverCron()由server.c中initServer()调用,initServer()由整个Redis主函数调用,在server.c文件最后面。
因为sentinel的各项功能大部分都在sentinelHandleDictOfRedisInstances(sentinel.masters);中实现,咱们主要分析这个函数。
sentinelHandleDictOfRedisInstances(sentinel.masters)
从上面能够看到,函数传进去了一个sentinel.masters的参数,这就迁出第一个问题,sentinel.masters是什么东西,从何而来。
Ctrl+左键能够看到定义masters的地方,其存在于一个sentinelState的结构体中,这个结构体定义了整个sentinel的状态结构,一个sentinel就是只存在一个sentinelState的实例。服务器
struct sentinelState { char myid[CONFIG_RUN_ID_SIZE+1]; /* 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? */ int running_scripts; /* Number of scripts in execution right now. */ mstime_t tilt_start_time; /* When TITL started. */ mstime_t previous_time; /* Last time we ran the time handler. */ 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. */ unsigned long simfailure_flags; /* Failures simulation. */ int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script paths at runtime? */ } sentinel;
其中定义一个masters的字典,顺着这个关键点,咱们引出第二个问题,sentinelState结构在哪里初始化,其中的masters字典中的数据结构是什么样子的。在redis的主函数里面,存在数据结构
/* We need to init sentinel right now as parsing the configuration file * in sentinel mode will have the effect of populating the sentinel * data structures with master nodes to monitor. */ if (server.sentinel_mode) { initSentinelConfig(); initSentinel(); }
在这个两个函数中完成了对sentinel状态结构的初始化,可是其中并无为sentinel.masters赋值的代码,这时应该能够想到,在sentinel的配置文件中定义了monitor监控的主服务器配置,咱们随即找到sentinel读取配置文件的函数,果真在redis的主函数中找到在入配置文件函数app
loadServerConfig(configfile, options);
loadServerConfig(configfile, options)–> loadServerConfigFromString(config), 其中对配置文件的各个部分进行解析,在匹配sentinel配置语句内发现为处理sentinel配置文件函数sentinelHandleConfiguration(argv+1,argc-1),这个函数就是sentinel配置文件分析器,在这里面终于找到了解析sentinel monitor 配置的语句。异步
if (!strcasecmp(argv[0],"monitor") && argc == 5) { /* monitor <name> <host> <port> <quorum> */ int quorum = atoi(argv[4]); 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"; } } }
又转进createSentinelRedisInstance(argv[1],SRI_MASTER,argv[2],atoi(argv[3]),quorum,NULL)函数,这就是为sentinel.masters结构赋值的函数,其中主要定义了ide
变量,接下来便看到函数
/* Make sure the entry is not duplicated. This may happen when the same * name for a master is used multiple times inside the configuration or * if we try to add multiple times a slave or sentinel with same ip/port * to a master. */ if (flags & SRI_MASTER) table = sentinel.masters; else if (flags & SRI_SLAVE) table = master->slaves; else if (flags & SRI_SENTINEL) table = master->sentinels;
这个table指针指向了sentinel.masters,以后就确定想的到修改table指针就能够直接修改sentinel.mastersui
/* Add into the right table. */ dictAdd(table, ri->name, ri);//将实例添加到适当的表中
能够看出,若是函数的实参flags == SRI_MASTER,就会建立一个 sentinelRedisInstance 结构,
并按照 /键sentinelRedisInstance.name /值sentinelRedisInstance的形式加入table字典,而table字典指向sentinel.masters字典,sentinel.masters存在于sentinelState结构体,一个sentinel只存在一个sentinelState实例,逻辑渐渐清晰,sentinelState中的sentinel.masters字典保存了全部的master实例。
既然看到这里,就引出第三个问题,createSentinelRedisInstance函数中的sentinelRedisInstance *ri;是什么? Sentinel 会为每一个被监视的 Redis 实例建立相应的 sentinelRedisInstance 实例(被监视的实例能够是主服务器、从服务器、或者其余 Sentinel )。spa
如今咱们把逻辑理一下,在createSentinelRedisInstance的形参中有两个重要的形参 int flags 和
sentinelRedisInstance *master
if (flags & SRI_MASTER) table = sentinel.masters; else if (flags & SRI_SLAVE) table = master->slaves; else if (flags & SRI_SENTINEL) table = master->sentinels;
根据flags个标志不一样,若是是SRI_MASTER,就建立一个flags = SRI_MASTER 的sentinelRedisInstance结构加入sentinel.masters中;若是是SRI_SLAVE, table指针就指向sentinel.masters的slaves变量,并建立slave实例加入sentinel.masters.slaves变量中;若是是SRI_SENTINEL,table指针就指向sentinel.masters的sentinels变量,并建立sentinel的sentinelRedisInstance实例加入sentinel.masters.slaves变量中。因此咱们大概能够想到sentinelHandleDictOfRedisInstances(sentinel.masters)函数只要传进一个sentinel.masters变量就能够遍历全部的master,slave,sentinel实例。
19.07.11
再看sentinel的运行过程。
第一个阶段:在Redis的主函数中调用initSentinelConfig();和initSentinel();函数主要完成对sentinelState实例sentinel的初始化。
第二个阶段:在Redis的主函数中调用loadServerConfig(configfile, options) -> loadServerConfigFromString(config) -> sentinelHandleConfiguration(argv+1,argc-1); 完成对sentinel配置文件的解析,在检测到 monitor, known-slave, known-sentinel配置时调用createSentinelRedisInstance()函数建立对应sentinelRedisInstance实例并根据known-slave、known-sentinel配置的master名字加入到对应相同名字的master sentinelRedisInstance实例结构中,不一样的master sentinelRedisInstance实例加入sentinelState实例的masters字典中,最后造成上图所示的结构。
第三个阶段(在此循环)
在Redis主函数中调用initServer(),在函数内部向事件循环中注册timer事件回调(serverCron函数)、能够理解为循环执行serverCron函数,在serverCron函数内部执行sentinel主函数sentinelTimer()。
第三个又一阶段
sentinel主函数内部执行sentinelHandleDictOfRedisInstances(sentinel.masters)函数,传入的实参是一个字典,遍历master字典,当遍历的实例标志为SRI_MASTER时再递归调用master的全部slave和sentinel,对全部实例执行调度操做。
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. */ di = dictGetIterator(instances); while((de = dictNext(di)) != NULL) { // 取出实例对应的实例结构 sentinelRedisInstance *ri = dictGetVal(de); // 对全部实例执行调度操做,最主要的操做 sentinelHandleRedisInstance(ri); if (ri->flags & SRI_MASTER) { // 递归slave sentinelHandleDictOfRedisInstances(ri->slaves); // 递归sentinel sentinelHandleDictOfRedisInstances(ri->sentinels); // 故障master的全部从服务器都已经同步到新主服务器 if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) { // 把故障 masters 赋值给 witch_to_promoted执行下面的操做 switch_to_promoted = ri; } } } // 故障master存在 if (switch_to_promoted) // 将故障master(已下线)从主服务器表格中移除,并使用新master代替它 sentinelFailoverSwitchToPromotedSlave(switch_to_promoted); dictReleaseIterator(di); }
第三个又一又一阶段
sentinelHandleRedisInstance(ri)函数对全部实例执行调度操做,
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) { /* ========== MONITORING HALF ============ */ /* Every kind of instance */ //可能建立这个哨兵连向遍历实例 ri 的网路链接sentinelReconnectInstance(ri); sentinelReconnectInstance(ri); //可能向实例发送 PING、 INFO 或者 PUBLISH 命令sentinelSendPeriodicCommands(ri); 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. */ * //// 若是 Sentinel 处于 TILT 模式,那么不执行故障检测。 if (sentinel.tilt) { if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return; sentinel.tilt = 0; sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited"); } /* Every kind of instance */ /* 检测实体是否主观断线 */ sentinelCheckSubjectivelyDown(ri); /* Masters and slaves */ if (ri->flags & (SRI_MASTER|SRI_SLAVE)) { /* Nothing so far. */ } /* Only masters */ if (ri->flags & SRI_MASTER) { /* Only masters 检查其是否进入客观下线状态*/ sentinelCheckObjectivelyDown(ri); // 检查其是知足故障转移的条件,知足条件时设置其故障转移标志位等待开始。 // master->failover_state = SENTINEL_FAILOVER_STATE_WAIT_START; if (sentinelStartFailoverIfNeeded(ri)) // 向master的其余sentinel发送异步命令 SENTINEL is-master-down-by-addr // 以得到容许达到法定人数的回复,就是请求其余哨兵投票。 sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED); // 根据master故障转移标志的不一样执行对应的操做,到这里表明能够进行故障转移 sentinelFailoverStateMachine(ri); // 这个函数跟上上面那个函数同样,但传入的标志不一样,这是给那些不须要故障转移的master使用。 sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS); } }
第三个又一又二阶段
因为在修改添加自定义配置时须要用到配置重写,在sentinel的各个阶段都有可能发生配置重写sentinelFlushConfig();sentinel的自动配置重写也是在这里运行,在添加自定义sentinel配置以后要在这个函数中加入相关重写函数,要否则会被刷掉
第三个又二阶段
判断这个sentinel须要执行操做
第四个阶段 在Redis主函数initServer();的下面还有一个sentinelIsRunning();函数,这个函数在 Sentinel 准备就绪,能够执行操做时执行,为这个哨兵建立惟一的ID并刷新在硬盘上面,为每个master生成 +monitor sentinelEvent事件。