天天看點

Redis源碼解析(16) 哨兵機制[3] 判斷下線

文章目錄

  • ​​引言​​
  • ​​sentinelHandleRedisInstance​​
  • ​​sentinelCheckSubjectivelyDown​​
  • ​​sentinelCheckObjectivelyDown​​
  • ​​sentinelAskMasterStateToOtherSentinels​​
  • ​​sentinelStartFailoverIfNeeded​​

​​Redis源碼解析(1) 動态字元串與連結清單​​

​​Redis源碼解析(2) 字典與疊代器​​

​​Redis源碼解析(3) 跳躍表​​

​​Redis源碼解析(4) 整數集合​​

​​Redis源碼解析(5) 壓縮清單​​

​​Redis源碼解析(6) 鍵的過期處理政策​​

​​Redis源碼解析(7) 釋出訂閱機制​​

​​Redis源碼解析(8) AOF持久化​​

​​Redis源碼解析(9) RDB持久化​​

​​Redis源碼解析(10) 網絡架構​​

​​Redis源碼解析(11) 記憶體淘汰政策​​

​​Redis源碼解析(12) 指令執行過程​​

​​Redis源碼解析(13) 主從複制​​

​​Redis源碼解析(14) 哨兵機制[1] 結構與初始化​​

​​Redis源碼解析(15) 哨兵機制[2] 資訊同步與TILT模式​​

Redis源碼解析(16) 哨兵機制[3] 判斷下線

​​Redis源碼解析(17) 哨兵機制[4] 故障轉移​​

​​Redis源碼解析(18) 叢集[1]初始化,握手與心跳檢測​​

​​Redis源碼解析(19) 叢集[2] 主從複制,故障檢測與故障轉移​​

​​Redis源碼解析(20) 叢集[3] 鍵的存儲,重新分片與重定向​​

​​Redis源碼解析(21) 叢集[4] 故障轉移failover與slave遷移​​

​​Redis源碼解析(22) 事務​​

​​Redis源碼解析(23) SCAN指令實作​​

引言

上一篇文章我們分析了如何使得sentinel在配置檔案中隻有主伺服器節點的情況下經過節點間通信,構成一個擁有主伺服器,從伺服器和sentinel節點的網狀結構.這篇文章來說說如何進行判斷下線,即一個主伺服器目前已經當機.

sentinelHandleRedisInstance

我們接着上一篇中​

​sentinelHandleRedisInstance​

​沒說完的地方繼續

void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {

    /* ========== MONITORING HALF ============ */
    /* ==========     監控操作    =========*/

    .......................
    /* ============== 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) {

        // 如果 TILI 模式未解除,那麼不執行動作 預設為PING間隔的30倍
        // PING間隔預設為一秒 但是配置檔案中判斷多長時間為下線的那個參數小于一秒時PING會更新為那個值
        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 */
    // 檢查給定執行個體是否進入 主觀下降 狀态 
    // 也就是在down_after時間内連續收到無效回複或者無回複時設定
    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 狀态,那麼開始一次故障轉移操作
        //裡面會執行sentinelStartFailover 變更狀态為SRI_FAILOVER_IN_PROGRESS
        if (sentinelStartFailoverIfNeeded(ri)) 
            // 強制向其他 Sentinel 發送 SENTINEL is-master-down-by-addr 指令
            // 重新整理其他 Sentinel 關于主伺服器的狀态
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);

        // 執行故障轉移
        // 第一次由主觀下降到客觀下降會執行sentinelAskMasterStateToOtherSentinels
        // 但同樣會執行sentinelFailoverStateMachine 因為狀态不對 第一條語句就會跳出
        sentinelFailoverStateMachine(ri);

        // 如果有需要的話,向其他 Sentinel 發送 SENTINEL is-master-down-by-addr 指令
        // 重新整理其他 Sentinel 關于主伺服器的狀态
        // 這一句是對那些沒有進入 if(sentinelStartFailoverIfNeeded(ri)) { /* ... */ }
        // 語句的主伺服器使用的
        // 顯然第一次由主觀下降到客觀下降會執行這個語句
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
    }
}      

這裡需要說的是​

​SRI_S_DOWN​

​​為主觀下降,即自己認為這個伺服器節點下線.​

​SRI_O_DOWN​

​為客觀下降,即發起投票後認為其下線.我們先列出這些函數的功能

函數名 功能

​sentinelCheckSubjectivelyDown​

判斷進入主觀下線

​sentinelCheckObjectivelyDown​

判斷進入客觀下線

​sentinelStartFailoverIfNeeded​

判斷是否進行故障轉移

​sentinelAskMasterStateToOtherSentinels​

根據目前flag狀态決定向其他sentinel發送判斷下線還是投票選舉

​sentinelFailoverStateMachine​

執行故障轉移 下一篇再談它

最有意思的是​

​sentinelAskMasterStateToOtherSentinels​

​,它可以根據狀态的不同來執行不同的功能,是以它出現了兩次.

sentinelCheckSubjectivelyDown

我們來看看如何判斷進入​

​主觀下線​

void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {

    mstime_t elapsed = 0;

    if (ri->last_ping_time) //執行個體最後一次發送PING指令的時間
        elapsed = mstime() - ri->last_ping_time;

    /* Check if we are in need for a reconnection of one of the 
     * links, because we are detecting low activity.
     *
     * 如果檢測到連接配接的活躍度(activity)很低,那麼考慮重斷開連接配接,并進行重連
     *
     * 1) Check if the command link seems connected, was connected not less
     *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
     *    pending ping for more than half the timeout. */
    // 考慮斷開執行個體的 cc 連接配接
    if (ri->cc &&
        (mstime() - ri->cc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        ri->last_ping_time != 0 && /* Ther is a pending ping... */
        /* The pending ping is delayed, and we did not received
         * error replies as well. */
        (mstime() - ri->last_ping_time) > (ri->down_after_period/2) &&
        (mstime() - ri->last_pong_time) > (ri->down_after_period/2)) 
        //連接配接時長已超過最短連接配接間隔 ping已經發出 但在down_after_period/2的時間内沒有收到pong
    {
        sentinelKillLink(ri,ri->cc); //斷開執行個體的連接配接
    }

    /* 2) Check if the pubsub link seems connected, was connected not less
     *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have no
     *    activity in the Pub/Sub channel for more than
     *    SENTINEL_PUBLISH_PERIOD * 3.
     */
    // 考慮斷開執行個體的 pc 連接配接
    if (ri->pc &&
        (mstime() - ri->pc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        (mstime() - ri->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3))
        //最後一次從這個伺服器接收消息的時間大于發送publish的最大時間的三倍,也就是6秒時斷開接收頻道資訊的連接配接
    {
        sentinelKillLink(ri,ri->pc); //斷開連接配接 設定flag為SRI_DISCONNECTED,
    }

    /* Update the SDOWN flag. We believe the instance is SDOWN if:
     *
     * 更新 SDOWN 辨別。如果以下條件被滿足,那麼 Sentinel 認為執行個體已下線:
     *
     * 1) It is not replying.
     *    它沒有回應指令
     * 2) We believe it is a master, it reports to be a slave for enough time
     *    to meet the down_after_period, plus enough time to get two times
     *    INFO report from the instance. 
     *    Sentinel 認為執行個體是主伺服器,這個伺服器向 Sentinel 報告它将成為從伺服器,
     *    但在超過給定時限之後,伺服器仍然沒有完成這一角色轉換。
     *    發送在主伺服器當機重連的時候
     */
    if (elapsed > ri->down_after_period || //現在距離上次ping的時間已經超過了down_after_period
        (ri->flags & SRI_MASTER &&
         ri->role_reported == SRI_SLAVE &&
         mstime() - ri->role_reported_time >
          (ri->down_after_period+SENTINEL_INFO_PERIOD*2))) //兩個INFO的間隔
    {
        /* Is subjectively down */
        if ((ri->flags & SRI_S_DOWN) == 0) {
            // 發送事件
            sentinelEvent(REDIS_WARNING,"+sdown",ri,"%@");
            // 記錄進入 SDOWN 狀态的時間
            ri->s_down_since_time = mstime();
            // 這一點很重要 判斷為主觀下線時更新SDOWN标志
            ri->flags |= SRI_S_DOWN; 
        }
    } else {
        // 移除(可能有的) SDOWN 狀态
        /* Is subjectively up */
        if (ri->flags & SRI_S_DOWN) {
            // 發送事件
            sentinelEvent(REDIS_WARNING,"-sdown",ri,"%@");
            // 移除相關标志
            ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
        }
    }
}      

我們可以在源碼中看到會在連接配接不活躍的時候采取重連措施.

判斷進入主觀下線有兩個條件

  1. 距離上次ping的時間已經超過了down_after_period,配置檔案中指定
  2. Sentinel 認為執行個體是主伺服器,這個伺服器向Sentinel報告它将成為從伺服器,且在兩個INFO指令間隔還是沒有轉換成功,認為其下線.

我們在來看看當判斷為​

​主觀下線​

​​時如果轉換為​

​客觀下線​

​.

我們來看看​

​sentinelCheckObjectivelyDown​

​​,其可以判斷master是否進入​

​客觀下線​

​​.先說一點在真的發生主觀下線的時候第一次執行這個函數隻有很小的機率可以使狀态轉為​

​客觀下線​

​​,因為它并沒有向其他sentinel節點詢問,隻是簡單的檢測目前sentinel中存儲的狀态而已.此時我們需要詢問其他sentinel,看看是否真的使得這個節點下線,就是​

​sentinelAskMasterStateToOtherSentinels​

​函數所做的事情.

sentinelCheckObjectivelyDown

void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
    dictIterator *di;
    dictEntry *de;
    int quorum = 0, odown = 0;

    // 如果目前 Sentinel 将主伺服器判斷為主觀下線
    // 那麼檢查是否有其他 Sentinel 同意這一判斷
    // 當同意的數量足夠時,将主伺服器判斷為客觀下線
    if (master->flags & SRI_S_DOWN) {
        /* Is down for enough sentinels? */

        // 統計同意的 Sentinel 數量(起始的 1 代表本 Sentinel)
        quorum = 1; /* the current sentinel. */

        /* Count all the other sentinels. */
        // 統計其他認為 master 進入下線狀态的 Sentinel 的數量
        di = dictGetIterator(master->sentinels); //得到監視這個伺服器的其他哨兵結點
        while((de = dictNext(di)) != NULL) {
            sentinelRedisInstance *ri = dictGetVal(de);
                
            // 該 SENTINEL 也認為 master 已下線
            if (ri->flags & SRI_MASTER_DOWN) quorum++;
        }
        dictReleaseIterator(di);
        
        // 如果投票得出的支援數目大于等于判斷 ODOWN 所需的票數
        // 那麼進入 ODOWN 狀态
        if (quorum >= master->quorum) odown = 1;
    }

    /* Set the flag accordingly to the outcome. */
    if (odown) {

        // master 已 ODOWN
        if ((master->flags & SRI_O_DOWN) == 0) {
            // 發送事件
            sentinelEvent(REDIS_WARNING,"+odown",master,"%@ #quorum %d/%d",
                quorum, master->quorum);
            // 狀态轉移為ODOWN 
            master->flags |= SRI_O_DOWN;
            // 記錄進入 ODOWN 的時間
            master->o_down_since_time = mstime();
        }
    } else {

        // 未進入 ODOWN

        if (master->flags & SRI_O_DOWN) {

            // 如果 master 曾經進入過 ODOWN 狀态,那麼移除該狀态

            // 發送事件
            sentinelEvent(REDIS_WARNING,"-odown",master,"%@");
            // 移除 ODOWN 标志
            master->flags &= ~SRI_O_DOWN;
        }
    }
}      

sentinelAskMasterStateToOtherSentinels

#define SENTINEL_ASK_FORCED (1<<0)
void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
    dictIterator *di;
    dictEntry *de;

    // 周遊正在監視相同 master 的所有 sentinel
    // 向它們發送 SENTINEL is-master-down-by-addr 指令
    di = dictGetIterator(master->sentinels);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);

        // 距離該 sentinel 最後一次回複 SENTINEL master-down-by-addr 指令已經過了多久
        mstime_t elapsed = mstime() - ri->last_master_down_reply_time;

        char port[32];
        int retval;

        /* If the master state from other sentinel is too old, we clear it. */
        // 如果目标 Sentinel 關于主伺服器的資訊已經太久沒更新,那麼我們清除它
        if (elapsed > SENTINEL_ASK_PERIOD*5) {
            ri->flags &= ~SRI_MASTER_DOWN;
            sdsfree(ri->leader);
            ri->leader = NULL;
        }

        /* Only ask if master is down to other sentinels if:
         *
         * 隻在以下情況滿足時,才向其他 sentinel 詢問主伺服器是否已下線
         *
         * 1) We believe it is down, or there is a failover in progress.
         *    本 sentinel 相信伺服器已經下線,或者針對該主伺服器的故障轉移操作正在執行
         * 2) Sentinel is connected.
         *    目标 Sentinel 與本 Sentinel 已連接配接
         * 3) We did not received the info within SENTINEL_ASK_PERIOD ms. 
         *    目前 Sentinel 在 SENTINEL_ASK_PERIOD 毫秒内沒有獲得過目标 Sentinel 發來的資訊
         * 4) 條件 1 和條件 2 滿足而條件 3 不滿足,但是 flags 參數給定了 SENTINEL_ASK_FORCED 辨別
         */
        if ((master->flags & SRI_S_DOWN) == 0) continue;
        if (ri->flags & SRI_DISCONNECTED) continue;
        if (!(flags & SENTINEL_ASK_FORCED) && //這個&&之前滿足的話證明這是狀态為主觀下線時調用
        // 現在的時間減去上次上次SENTINEL指令回複之間時間小于1秒 
        // 原因是投票可能一輪不會出現leader 防止每次事件循環都會投票 是以設定最多一秒一次
            mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD)
            continue;

        /* Ask */
        // 發送 SENTINEL is-master-down-by-addr 指令
        ll2string(port,sizeof(port),master->addr->port);
        retval = redisAsyncCommand(ri->cc,
                    sentinelReceiveIsMasterDownReply, NULL,
                    //                          [IP][port][配置紀元][runid/*]
                    // 最後一項上為'*'代表這條指令僅僅用于檢測伺服器的客觀下線
                    "SENTINEL is-master-down-by-addr %s %s %llu %s",
                    master->addr->ip, port,
                    sentinel.current_epoch,
                    // 如果本 Sentinel 已經檢測到 master 進入 ODOWN 
                    // 并且要開始一次故障轉移,那麼向其他 Sentinel 發送自己的運作 ID
                    // 讓對方将給自己投一票(如果對方在這個紀元内還沒有投票的話)
                    // 當我們設定flag為客觀下線時會設定failover_state為SENTINEL_FAILOVER_STATE_WAIT_START
                    // 即滿足下述條件 開始選舉leader
                    (master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
                    server.runid : "*"); //
        if (retval == REDIS_OK) ri->pending_commands++;
    }
    dictReleaseIterator(di);
}      

我們可以最後發送指令時會根據​

​failover_state​

​​來決定發​

​server.runid​

​​ 還是 “*”,前者代表請求投票自己為leader,後者代表這隻是一次判斷是否進行客觀下線,而在判斷了​

​客觀下線​

​​後狀态且進入故障轉移的時候會設定為​

​SENTINEL_FAILOVER_STATE_WAIT_START​

​​其大于​

​SENTINEL_FAILOVER_STATE_NONE​

​​,滿足條件.是以在轉換為​

​客觀下線​

​​之前會向其他sentinel節點發送消息,請求判斷這個節點是否真的下線,在下一次進入​

​sentinelHandleRedisInstance​

​​的時候,​

​sentinelCheckSubjectivelyDown​

​就有效了.

sentinelStartFailoverIfNeeded

int sentinelStartFailoverIfNeeded(sentinelRedisInstance *master) {

    /* We can't failover if the master is not in O_DOWN state. */
    // 客觀下線直接退出 這就是第一次進入sentinelHandleRedisInstance不執行的原因
    if (!(master->flags & SRI_O_DOWN)) return 0; 

    /* Failover already in progress? */
    if (master->flags & SRI_FAILOVER_IN_PROGRESS) return 0;

    /* Last failover attempt started too little time ago? */
    // 上一次故障轉移嘗試開始的時間太短
    if (mstime() - master->failover_start_time <
        master->failover_timeout*2)
    {
        if (master->failover_delay_logged != master->failover_start_time) {
            time_t clock = (master->failover_start_time +
                            master->failover_timeout*2) / 1000;
            char ctimebuf[26];

            ctime_r(&clock,ctimebuf);
            ctimebuf[24] = '\0'; /* Remove newline. */
            master->failover_delay_logged = master->failover_start_time;
            redisLog(REDIS_WARNING,
                "Next failover delay: I will not start a failover before %s",
                ctimebuf);
        }
        return 0;
    }

    // 開始一次故障轉移 此時已經确定這個節點是要下線的了
    sentinelStartFailover(master); //伺服器flag變更為SRI_FAILOVER_IN_PROGRESS
                                //初始狀态為SENTINEL_FAILOVER_STATE_WAIT_START
                                //狀态轉移很重要

    return 1;
}