redis哨兵主從切換過程解析


redis哨兵主從切換過程解析

redis主掛掉,從節點能升級為主的前置條件

  • redis 主節點
    • 狀態為 SRI_O_DOWN,主節點master被標記為客觀下線
  • redis從節點
    • 從節點沒有處於主觀下線、客觀下線或者斷鏈狀態;
    • 距離上一次收到該從節點對於"PING"命令的正常回復的時間,不超過5倍的SENTINEL_PING_PERIOD;
    • 該從節點的優先級不是0;
    • 距離上一次收到該從節點對於"INFO"命令的回復的時間,不超過3倍或5倍(根據主節點是否客觀下線而定)的SENTINEL_PING_PERIOD;
    • 從節點與主節點的斷鏈時間(該時間值根據從節點的"INFO"命令回復中得到)不超過max_master_down_time;
    • 所有從節點都輪訓完畢之后,使用qsort快速排序算法,對數組instance進行排序。這里使用的比較函數compareSlavesForPromotion;排好序的instance數組,狀態越好的從節點,其位置越靠前,因此,返回instance[0]作為選中的從節點;
    • 當選擇好一個從節點之后,接下來在狀態為SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE時,要做的就是向該從節點發送”SLAVEOF NO ONE”命令。
  • 所有主從哨兵模式,如果從節點無法升級為主節點,在哨兵節點正常的情況下,都可以通過維修主節點正常啟動完成主從集群的修復。

redis哨兵切換主從過程解析

1.redis主節點主觀下線、客觀下線

哨兵每隔一段時間,會向其所監控的所有實例發送一些命令,用於獲取這些實例的狀態。這些命令包括:”PING”、”INFO”和”PUBLISH”。

​ “PING”命令,主要用於哨兵探測實例是否活着。如果對方超過一段時間,還沒有回復”PING”命令,則認為其是主觀下線了。

​ “INFO”命令,主要用於哨兵獲取實例當前的狀態和信息,比如該實例當前是主節點還是從節點;該實例反饋的IP地址和PORT信息,是否與我記錄的一樣;該實例如果是主節點的話,那它都有哪些從節點;該實例如果是從節點的話,它與主節點是否連通,它的優先級是多少,它的復制偏移量是多少等等,這些信息在故障轉移流程中,是判斷實例狀態的重要信息;

​ “PUBLISH”命令,主要用於哨兵向實例的HELLO頻道發布有關自己以及主節點的信息,也就是所謂的HELLO消息。因為所有哨兵都會訂閱主節點和從節點的HELLO頻道,因此,每個哨兵都會收到其他哨兵發布的信息。

​ 因此,通過這些命令,盡管在配置文件中只配置了主節點的信息,但是哨兵可以通過主節點的”INFO”回復,得到所有從節點的信息;又可以通過訂閱實例的HELLO頻道,接收其他哨兵通過”PUBLISH”命令發布的信息,從而得到監控同一主節點的所有其他哨兵的信息。

redis哨兵源碼分析

sentinel.c

  • sentinelHandleDictOfRedisInstances
    • sentinelHandleRedisInstance
      • sentinelReconnectInstance
        • 重連 重連失敗 關閉連接
        • 重連成功 發送ping命令
      • sentinelSendPeriodicCommands
        • Send periodic PING, INFO, and PUBLISH to the Hello channel to the specified master or slave instance
        • 所有節點
          • 實例超時則關閉連接,(ri->link->cc_conn_time / ri->link->act_ping_time / ri->link->last_pong_time / ri->link->pc_last_activity / ) 關閉連接
          • if ((ri->flags & SRI_S_DOWN) == 0) -----> sentinelEvent(LL_WARNING,"+sdown",ri,"%@");
        • 主節點
          • 主節點 SRI_S_DOWN 后,查詢主節點的所有sentinel節點,sentinel節點投票主節點下線數大於 主節點定義的投票數,則 SRI_O_DOWN
          • 主節點SRI_O_DOWN后才會觸發遷移
          • 判斷實例是否客觀下線
            • 當前哨兵一旦監測到某個主節點實例主觀下線之后,就會向其他哨兵發送”is-master-down-by-addr”命令,詢問其他哨兵是否也認為該主節點主觀下線了。如果有超過quorum個哨兵(包括當前哨兵)反饋,都認為該主節點主觀下線了,則當前哨兵就將該主節點實例標記為客觀下線。客觀下線的概念只針對主節點實例,而與從節點和哨兵實例無關。
            • 發送”is-master-down-by-addr”命令。”is-master-down-by-addr”命令有兩個作用:一是詢問其他哨兵是否認為某個主節點已經主觀下線;二是開始故障遷移時,當前哨兵向其他哨兵實例進行"拉票",讓其選自己為領導節點。

2.redis sentinel哨兵節點選舉領導節點

1:故障轉移流程

​ 當哨兵監測到某個主節點客觀下線之后,就會開始故障轉移流程。具體步驟就是:

​ a:在所有哨兵中發起一次“選舉”,讓其他哨兵選擇“我”(當前哨兵)為領導節點;

​ b:如果“我”能贏得大部分的選票,也就是在共有n個哨兵節點的情況下,如果有超過n/2個哨兵都將選票投給了“我”,則“我”就贏得了本界選舉,成為領導節點,從而可以繼續下面的流程。如果我沒有贏得本界選舉,則不能進行下面的流程了,而是隨機等待一段時間后,開始下一輪選舉;

​ c:“我”贏得選舉后,就會從客觀下線主節點的所有下屬從節點中,按照一定規則選擇一個從節點,使其升級為新的主節點;

​ d:當選中的從節點升級為主節點之后,“我”就會向剩下的從節點發送”SLAVEOF”命令,使它們與新的主節點進行同步;

​ e:最后,更新新主節點的信息,並通過”PUBLISH”命令,將新主節點的信息傳播給其他哨兵。

2:選舉領導節點原理

​ 故障轉移流程中,最難理解的部分就是選舉領導節點的過程。因為多個哨兵實際上是組成了一個分布式系統,它們之間需要相互協作,通過交換信息,最終選出一個領導節點。

​ sentinel選舉的過程,借鑒了分布式系統中的Raft協議。Raft協議是用來解決分布式系統一致性問題的協議,在很長一段時間,Paxos被認為是解決分布式系統一致性的代名詞。但是Paxos難於理解,更難以實現。而Raft協議設計的初衷就是容易實現,保證對於普遍的人群都可以十分舒適容易的去理解。

​ 有關Raft算法,可以參考官網https://raft.github.io/中的介紹。如果想要以最快的速度了解Raft算法的基本原理,可以參考這個PPT,非常形象且容易理解:http://thesecretlivesofdata.com/raft/

​ 要理解哨兵的選舉過程,關鍵就在於理解選舉紀元(epoch)的概念。所謂的選舉紀元,直白的解釋就是“第幾屆選舉”。

​ 選舉紀元實際上就是一個計數器。當哨兵進程啟動時,其選舉紀元就被初始化,默認的初始化值為0,不過該值也可以在配置文件中進行配置。

​ 哨兵運行起來之后,哨兵之間通過HELLO消息來交換信息。HELLO消息中,除了有主節點信息之外,還包含哨兵本地的選舉紀元值(sentinel.current_epoch)。當哨兵收到其他哨兵發布的HELLO消息后,解析其中的選舉紀元值,如果該值大於“我”本地的選舉紀元值,則會用它的選舉紀元更新“我”的選舉紀元。

​ 因此,同一個監控單位內的所有哨兵,他們的選舉紀元最終就會達成一個統一的值,這也就是Raft中,最終一致性的意思。

​ 當哨兵A發現某個主節點客觀下線后,它就會發起新一屆的選舉。第一件事就是將本地的選舉紀元加1,這個加1的意思,實際上就是表示“發起新一屆選舉”。之后,哨兵A就會向其他哨兵發送”is-master-down-by-addr”命令,用於拉票,其中就包含了A的選舉紀元。

​ 投票采用先到先得的策略,因此當哨兵B收到A發來的”is-master-down-by-addr”命令之后,得到A的選舉紀元,如果其值大於本地的選舉紀元,說明本界選舉中還沒有投過票,則會更新本地的選舉紀元,同時把票投給A。

​ 現實當然不會這么簡單,分布式系統因為涉及多個機器,就會有各種可能的情況發生。比如哨兵C幾乎同時也發起了新一屆的選舉,它也會把本地的選舉紀元加1,並發送”is-master-down-by-addr”命令。當B收到C發來的命令之后,得到C的選舉紀元,發現其值並不大於本地的選舉紀元(因為剛才已經根據A的選舉紀元更新了),因此就不會再次投票了,而是將之前投票給A的結果反饋給C。

​ 通過上面的介紹可知,在同一屆選舉(同一個選舉紀元的值)中,每個哨兵只會投一次票。因此,在一界選舉中,只可能有一個哨兵能獲得超過半數的投票,從而贏得選舉。

​ 當然,也有可能產生選舉失敗的情況。也就是沒有一個哨兵能獲得超過半數的投票。比如有4個哨兵節點A、B、C、D。哨兵A和C幾乎同時發起了新的選舉,最終B和C將選票投給了A,而A和D將選票投給了C。因此,A和C都只得到了2票,沒有超過半數,因此都不能成為新的領導節點。這種情況下,A和C都會隨機等待一段時間之后,重新發起新的選舉。這種隨機性能減少下一輪選舉的沖突,從而降低選舉失敗的可能。

3. redis 故障轉移流程

是否能開始一次新的故障轉移流程,需要滿足下面三個條件:

​ a:主節點master被標記為客觀下線了;

​ b:當前沒有針對該master進行故障轉移流程;

​ c:最重要的條件是,針對該master,當前時間與master->failover_start_time之間的時間差,已經超過了master->failover_timeout*2。也就是說,當前距離上次進行故障轉移流程的開始時間,或者是距離上次投票給其他哨兵的時間,已經等待了足夠長的時間;

故障轉移流程中的狀態轉換(6個狀態)
SENTINEL_FAILOVER_STATE_WAIT_START
  • 一旦哨兵開始一次故障轉移流程時,該哨兵第一件事就是向其他所有哨兵發送”is-master-down-by-addr”命令進行拉票。然后就是調用sentinelFailoverWaitStart函數處理當前狀態。
  • 當前哨兵,在調用sentinelStartFailover函數發起故障轉移流程時,會將當前選舉紀元sentinel.current_epoch記錄到ri->failover_epoch中。因此,本函數首先根據ri->failover_epoch,調用函數sentinelGetLeader得到本界選舉的結果leader。如果本界選舉尚無人獲得超過半數的選票,則leader為NULL;
  • 如果當前哨兵還沒有贏得選舉,並且主節點標志位中沒有設置SRI_FORCE_FAILOVER標記,說明當前哨兵還沒有獲得足夠的選票,暫時不能繼續進行接下來的故障轉移流程,需要直接返回。
  • 但是如果超過一定時間之后,當前哨兵還是沒有贏得選舉,則會終止當前的故障轉移流程,因此如果當前距離開始故障轉移的時間超過election_timeout,則調用函數sentinelAbortFailover,終止本次故障轉移流程。
  • 如果當前哨兵最終贏得了選舉,則更新故障轉移的狀態,置ri->failover_state屬性為下一個狀態:SENTINEL_FAILOVER_STATE_SELECT_SLAVE,並更新ri->failover_state_change為當前時間;
SENTINEL_FAILOVER_STATE_SELECT_SLAVE
  • 當故障轉移狀態轉換為SENTINEL_FAILOVER_STATE_SELECT_SLAVE時,就需要在下線主節點的所有下屬從節點中,按照一定的規則,選擇一個從節點使其成為新的主節點。

  • 該函數首先調用函數sentinelSelectSlave選擇一個符合條件的從節點;

  • 如果沒有合適的從節點,則調用sentinelAbortFailover直接終止本次故障轉移流程;

  • 如果找到了合適的從節點slave,則首先將標記SRI_PROMOTED增加到該從節點的標志位中;並使主節點實例的ri->promoted_slave指針指向該從節點實例,並將故障轉移狀態置為SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE;然后更新ri->failover_state_change_time為當前時間;

    選擇合適的slave節點的規則如下
    • 從節點沒有處於主觀下線、客觀下線或者斷鏈狀態;
    • 距離上一次收到該從節點對於"PING"命令的正常回復的時間,不超過5倍的SENTINEL_PING_PERIOD;
    • 該從節點的優先級不是0;
    • 距離上一次收到該從節點對於"INFO"命令的回復的時間,不超過3倍或5倍(根據主節點是否客觀下線而定)的SENTINEL_PING_PERIOD;
    • 從節點與主節點的斷鏈時間(該時間值根據從節點的"INFO"命令回復中得到)不超過max_master_down_time;
    • 所有從節點都輪訓完畢之后,使用qsort快速排序算法,對數組instance進行排序。這里使用的比較函數compareSlavesForPromotion;排好序的instance數組,狀態越好的從節點,其位置越靠前,因此,返回instance[0]作為選中的從節點;
    • 當選擇好一個從節點之后,接下來在狀態為SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE時,要做的就是向該從節點發送”SLAVEOF NO ONE”命令。
SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE
  • 首先如果選中的從節點當前處於斷鏈狀態,則因無法向其發送命令,因此直接返回;如果該狀態已經持續超過ri->failover_timeout的時間,則調用函數sentinelAbortFailover終止本次故障轉移流程;
  • 調用sentinelSendSlaveOf函數,向從節點發送"SLAVEOF NO ONE"命令,然后置故障轉移狀態為SENTINEL_FAILOVER_STATE_WAIT_PROMOTION,並且更新ri->failover_state_change_time為當前時間;
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION
  • 判斷處於SENTINEL_FAILOVER_STATE_WAIT_PROMOTION狀態的時間是否超過了閾值ri->failover_timeout。如果確實已經超過了,則調用函數sentinelAbortFailover終止本次故障轉移流程;
SENTINEL_FAILOVER_STATE_RECONF_SLAVES
  • 當故障轉移狀態變為SENTINEL_FAILOVER_STATE_RECONF_SLAVES時,選中的從節點已經升級為主節點,接下來要做的就是向其他從節點發送”SLAVEOF”命令,使它們與新的主節點進行同步。
  • 因為從節點在與主節點進行同步時,有可能無法響應客戶端的查詢。因此為了避免過多從節點因為同步而無法響應的問題,一個時間段內,最多只能允許master->parallel_syncs個從節點正在進行同步操作;
SENTINEL_FAILOVER_STATE_UPDATE_CONFIG
  • 故障轉移流程的最后一個狀態,就是要更新當前哨兵節點中的主節點實例,及其下屬從節點實例的信息。

參考資料如下:
https://www.cnblogs.com/gqtcgq/p/7247048.html
https://www.cnblogs.com/gqtcgq/p/7247047.html
https://www.cnblogs.com/gqtcgq/p/7247046.html

redis 5.0.8 源碼
源碼閱讀工具 Understand 5.1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM