Redis5源碼解析-Sentinel

 

簡單的概念就不解釋。基於Redis5.0.5node

從Sentinel主函數觸發


在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

  • sentinelRedisInstance *ri;
  • dict *table = NULL;

變量,接下來便看到函數

/* 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)函數對全部實例執行調度操做,

  • 可能建立這個哨兵連向遍歷實例 ri 的網路鏈接sentinelReconnectInstance(ri);
  • 可能向實例發送 PING、 INFO 或者 PUBLISH 命令sentinelSendPeriodicCommands(ri);
  • 檢查實例是否主觀下線sentinelCheckSubjectivelyDown(ri);
  • 當遍歷的sentinelRedisInstance實例爲master時,判斷其是否進入客觀下線狀態並執行相應的故障轉移。
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須要執行操做

  • 判斷是否須要進入 TITL 模式sentinelCheckTiltCondition();
  • 運行等待執行的腳本sentinelRunPendingScripts();
  • 清理已執行完畢的腳本,並重試出錯的腳本sentinelCollectTerminatedScripts();
  • 殺死運行超時的腳本sentinelKillTimedoutScripts();
  • server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;是爲了防止哨兵同時啓動同時請求對方投票形成沒有哨兵能夠成爲領導者。split brain voting

第四個階段 在Redis主函數initServer();的下面還有一個sentinelIsRunning();函數,這個函數在 Sentinel 準備就緒,能夠執行操做時執行,爲這個哨兵建立惟一的ID並刷新在硬盤上面,爲每個master生成 +monitor sentinelEvent事件。

相關文章
相關標籤/搜索