首頁 > 軟體

Redis叢集節點通訊過程/原理流程分析

2022-03-18 13:02:18

簡介

        本文介紹Redis的Cluster(叢集)的節點通訊的流程。

通訊流程

        在分散式儲存中需要提供維護節點後設資料資訊的機制, 所謂後設資料是指: 節點負責哪些資料, 是否出現故障等狀態資訊。 常見的後設資料維護方式分為: 集中式和P2P方式。 Redis叢集採用P2P的Gossip(流言) 協定,Gossip協定工作原理就是節點彼此不斷通訊交換資訊, 一段時間後所有的節點都會知道叢集完整的資訊, 這種方式類似流言傳播, 如下所示

通訊過程說明:

  • 叢集中的每個節點都會單獨開闢一個TCP通道, 用於節點之間彼此通訊, 通訊埠號在基礎埠上加10000。
  • 每個節點在固定週期內通過特定規則選擇幾個節點傳送ping訊息。接收到ping訊息的節點用pong訊息作為響應。
  • 叢集中每個節點通過一定規則挑選要通訊的節點, 每個節點可能知道全部節點, 也可能僅知道部分節點, 只要這些節點彼此可以正常通訊, 最終它們會達到一致的狀態。 當節點出故障、 新節點加入、 主從角色變化、 槽資訊變更等事件發生時, 通過不斷的ping/pong訊息通訊, 經過一段時間後所有的節點都會知道整個叢集全部節點的最新狀態, 從而達到叢集狀態同步的目的。 

Gossip訊息

訊息流程

        Gossip協定的主要職責就是資訊交換。 資訊交換的載體就是節點彼此傳送的Gossip訊息, 瞭解這些訊息有助於我們理解叢集如何完成資訊交換。

        常用的Gossip訊息可分為: ping訊息、 pong訊息、 meet訊息、 fail訊息等, 它們的通訊模式如下圖所示:

  • meet訊息: 用於通知新節點加入。

訊息傳送者通知接收者加入到當前叢集, meet訊息通訊正常完成後, 接收節點會加入到叢集中並進行週期性的ping、 pong訊息交換。

  • ping訊息: 叢集內交換最頻繁的訊息

 叢集內每個節點每秒向多個其他節點傳送ping訊息, 用於檢測節點是否線上和交換彼此狀態資訊。 ping訊息傳送封裝了自身節點和部分其他節點的狀態資料。

  • pong訊息: 當接收到ping、 meet訊息時, 作為響應訊息回覆給傳送方確認訊息正常通訊。

pong訊息內部封裝了自身狀態資料。 節點也可以向叢集內廣播自身的pong訊息來通知整個叢集對自身狀態進行更新。
fail訊息: 當節點判定叢集內另一個節點下線時, 會向叢集內廣播一個fail訊息, 其他節點接收到fail訊息之後把對應節點更新為下線狀態。 具體細節將在後面“故障轉移”中說明。

訊息格式

        所有的訊息格式劃分為: 訊息頭和訊息體。 訊息頭包含傳送節點自身狀態資料, 接收節點根據訊息頭就可以獲取到傳送節點的相關資料, 結構如下:

typedef struct {
    char sig[4]; /* 訊號標示 */
    uint32_t totlen; /* 訊息總長度 */
    uint16_t ver; /* 協定版本*/
    uint16_t type; /* 訊息型別,用於區分meet,ping,pong等訊息 */
    uint16_t count; /* 訊息體包含的節點數量, 僅用於meet,ping,ping訊息型別*/
    uint64_t currentEpoch; /* 當前傳送節點的設定紀元 */
    uint64_t configEpoch; /* 主節點/從節點的主節點設定紀元 */
    uint64_t offset; /* 複製偏移量 */
    char sender[CLUSTER_NAMELEN]; /* 傳送節點的nodeId */
    unsigned char myslots[CLUSTER_SLOTS/8]; /* 傳送節點負責的槽資訊 */
    char slaveof[CLUSTER_NAMELEN]; /* 如果傳送節點是從節點, 記錄對應主節點的nodeId */
    uint16_t port; /* 埠號 */
    uint16_t flags; /* 傳送節點標識,區分主從角色, 是否下線等 */
    unsigned char state; /* 傳送節點所處的叢集狀態 */
    unsigned char mflags[3]; /* 訊息標識 */
    union clusterMsgData data /* 訊息正文 */;
} clusterMsg;

        叢集內所有的訊息都採用相同的訊息頭結構clusterMsg, 它包含了傳送節點關鍵資訊, 如節點id、 槽對映、 節點標識(主從角色, 是否下線) 等。訊息體在Redis內部採用clusterMsgData結構宣告, 結構如下:

union clusterMsgData {
    /* ping,meet,pong訊息體*/
    struct {
        /* gossip訊息結構陣列 */
        clusterMsgDataGossip gossip[1];
    } ping;
    
    /* FAIL 訊息體 */
    struct {
        clusterMsgDataFail about;
    } fail;
    // ...
};

        訊息體clusterMsgData定義傳送訊息的資料, 其中ping、 meet、 pong都採用cluster MsgDataGossip陣列作為訊息體資料, 實際訊息型別使用訊息頭的type屬性區分。 每個訊息體包含該節點的多個clusterMsgDataGossip結構資料, 用於資訊交換, 結構如下:

typedef struct {
    char nodename[CLUSTER_NAMELEN]; /* 節點的nodeId */
    uint32_t ping_sent; /* 最後一次向該節點傳送ping訊息時間 */
    uint32_t pong_received; /* 最後一次接收該節點pong訊息時間 */
    char ip[NET_IP_STR_LEN]; /* IP */
    uint16_t port; /* port*/
    uint16_t flags; /* 該節點標識, */
} clusterMsgDataGossip;

        當接收到ping、 meet訊息時, 接收節點會解析訊息內容並根據自身的識別情況做出相應處理, 對應流程如下圖所示:

接收節點收到ping/meet訊息時, 執行解析訊息頭和訊息體流程:

  • 解析訊息頭過程:

訊息頭包含了傳送節點的資訊, 如果傳送節點是新節點且訊息是meet型別, 則加入到本地節點列表; 如果是已知節點, 則嘗試更新傳送節點的狀態, 如槽對映關係、 主從角色等狀態。

  • 解析訊息體過程:

如果訊息體的clusterMsgDataGossip陣列包含的節點是新節點, 則嘗試發起與新節點的meet握手流程; 如果是已知節點, 則根據cluster MsgDataGossip中的flags欄位判斷該節點是否下線, 用於故障轉移。
訊息處理完後回覆pong訊息, 內容同樣包含訊息頭和訊息體, 傳送節點接收到回覆的pong訊息後, 採用類似的流程解析處理訊息並更新與接收節點最後通訊時間, 完成一次訊息通訊。

節點選擇

        雖然Gossip協定的資訊交換機制具有天然的分散式特性, 但它是有成本的。 由於內部需要頻繁地進行節點資訊交換, 而ping/pong訊息會攜帶當前節點和部分其他節點的狀態資料, 勢必會加重頻寬和計算的負擔。 Redis叢集內節點通訊採用固定頻率(定時任務每秒執行10次) 。 因此節點每次選擇需要通訊的節點列表變得非常重要。 通訊節點選擇過多雖然可以做到資訊及時交換但成本過高。 節點選擇過少會降低叢集內所有節點彼此資訊交換頻率,從而影響故障判定、 新節點發現等需求的速度。 因此Redis叢集的Gossip協定需要兼顧資訊交換實時性和成本開銷, 通訊節點選擇的規則如下圖所示

        根據通訊節點選擇的流程可以看出訊息交換的成本主要體現在單位時間選擇傳送訊息的節點數量和每個訊息攜帶的資料量。

1.選擇傳送訊息的節點數量

        叢集內每個節點維護定時任務預設每秒執行10次, 每秒會隨機選取5個節點找出最久沒有通訊的節點傳送ping訊息, 用於保證Gossip資訊交換的隨機性。 每100毫秒都會掃描本地節點列表, 如果發現節點最近一次接受pong訊息的時間大於cluster_node_timeout/2, 則立刻傳送ping訊息, 防止該節點資訊太長時間未更新。 根據以上規則得出每個節點每秒需要傳送ping訊息的數
量=1+10*num(node.pong_received>cluster_node_timeout/2) , 因此cluster_node_timeout引數對訊息傳送的節點數量影響非常大。 當我們的頻寬資源緊張時, 可以適當調大這個引數, 如從預設15秒改為30秒來降低頻寬佔用率。 過度調大cluster_node_timeout會影響訊息交換的頻率從而影響故障轉移、 槽資訊更新、 新節點發現的速度。 因此需要根據業務容忍度和資源消耗進行平衡。 同時整個叢集訊息總交換量也跟節點數成正比。

2.訊息資料量

        每個ping訊息的資料量體現在訊息頭和訊息體中, 其中訊息頭主要佔用空間的欄位是myslots[CLUSTER_SLOTS/8], 佔用2KB, 這塊空間佔用相對固定。 訊息體會攜帶一定數量的其他節點資訊用於資訊交換。 具體數量見以下虛擬碼:

def get_wanted():
    int total_size = size(cluster.nodes)
    
    # 預設包含節點總量的1/10
    594int wanted = floor(total_size/10);
    if wanted < 3:
    # 至少攜帶3個其他節點資訊
    wanted = 3;
    if wanted > total_size -2 :
    # 最多包含total_size - 2個
    wanted = total_size - 2;
    return wanted;

        根據虛擬碼可以看出訊息體攜帶資料量跟叢集的節點數息息相關, 更大的叢集每次訊息通訊的成本也就更高, 因此對於Redis叢集來說並不是大而全的叢集更好, 對於叢集規模控制的建議見之後“叢集運維”。

其他網址

《Redis開發與運維》=> 第10章 叢集=> 10.3 節點通訊

到此這篇關於Redis叢集節點通訊過程/原理的文章就介紹到這了,更多相關Redis叢集節點通訊內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com