簡單的概念就不解釋。基於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事件。