【Redis 源碼】集羣之分佈式 cluster 原理

前言:

接着上一章開始講,上一章我們講到如果配置一個 cluster,以及 cluster 的基礎命令。以及初始化一個 cluster 的流程。本章繼續講一些 cluster 原理。

(一) 消息結構

1.1 數據包類型

以下是數據包的類型:

#define CLUSTERMSG_TYPE_PING 0          /* Ping包類型 */
#define CLUSTERMSG_TYPE_PONG 1          /* Pong包類型 (Ping的返回信息) */
#define CLUSTERMSG_TYPE_MEET 2          /* meet包類型 */
#define CLUSTERMSG_TYPE_FAIL 3          /* fail包類型 */
#define CLUSTERMSG_TYPE_PUBLISH 4       /* 發佈訂閱消息包類型 */
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* failover授權請求包 */
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6     /* failover授權確認包 */
#define CLUSTERMSG_TYPE_UPDATE 7        /* update包,用於更新配置使用 */
#define CLUSTERMSG_TYPE_MFSTART 8       /* 手動failover包 */
#define CLUSTERMSG_TYPE_COUNT 9         /* 消息類型總數,用於計算邊界. */

數據包類型在 redis4.0 中分爲 9 總,redis5.0 會多一種。其中 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST、CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK、CLUSTERMSG_TYPE_MFSTART 三種包是沒有包體結構的,也就是 5,6,8 類型。

1.2 clusterMsg 消息結構:

typedef struct {
    char sig[4];        /* Siganture "RCmb" (Redis Cluster message bus). */
    uint32_t totlen;    /* 消息總長度 */
    uint16_t ver;       /* 協議版本,當前設置爲1。*/
    uint16_t port;      /* TCP端口號. */
    uint16_t type;      /* 消息類型 */
    uint16_t count;     /* data中的gossip session個數。(只在發送MEET、PING和PONG這三種消息時使用) */
    uint64_t currentEpoch;  /* 消息發送者紀元 */
    uint64_t configEpoch;   /* 如果消息發送者是一個主節點,那麼該項爲消息發送者配置紀元。
                              如果消息發送者是一個從節點,那麼該項爲發送者正在複製的主節點紀元。*/
    uint64_t offset;                        /* 複製偏移量. */
    char sender[CLUSTER_NAMELEN];           /* 發送節點名稱 */
    unsigned char myslots[CLUSTER_SLOTS/8]; /*消息發送者目前的槽指派信息*/
    char slaveof[CLUSTER_NAMELEN];          /*發送方如果是從,對應主的名稱。*/
    char myip[NET_IP_STR_LEN];              /* 發送人IP地址,如果不是全部爲0. */
    char notused1[34];                      /* 34 字節保留字節 */
    uint16_t cport;                         /* 發送方集羣TCP 端口 */
    uint16_t flags;                         /* 發送人節點標誌 */
    unsigned char state;                    /* 消息發送者所在集羣的狀態 */
    unsigned char mflags[3];                /* 消息標誌: CLUSTERMSG_FLAG[012]_... */
    union clusterMsgData data;              /* 消息包內容 */
} clusterMsg;

clusterMsg 結構中 data 對應包的內容,對應 clusterMsgData 結構這個是一個 union 結構。包的類型會根據 type 的不同映射對應的結構。

1.3 clusterMsgData 結構:

union clusterMsgData {
    /* PING, MEET和 PONG包內容, 包是一個clusterMsgDataGossip結構數組。*/
    struct {
      
        clusterMsgDataGossip gossip[1];
    } ping;

    /* FAIL包內容 */
    struct {
        clusterMsgDataFail about;
    } fail;

    /* PUBLISH包內容 */
    struct {
        clusterMsgDataPublish msg;
    } publish;

    /* UPDATE包內容 */
    struct {
        clusterMsgDataUpdate nodecfg;
    } update;
};

clusterMsgData 結構體包含 ping、fail、publish、update 四種結構。其中 ping 結構提供給三種類型包使用,分別是 ping、meet 和 pong。

1.4 clusterMsgDataGossip 結構:

typedef struct {
    char nodename[CLUSTER_NAMELEN];  //節點名稱
    uint32_t ping_sent;              //發送ping時間
    uint32_t pong_received;          //返回pong時間
    char ip[NET_IP_STR_LEN];    /* 節點ip地址 */
    uint16_t port;              /* 節點端口 */
    uint16_t cport;             /* 節點監聽集羣端口 */
    uint16_t flags;             /* 節點狀態 node->flags copy */
    uint32_t notused1;         //預留
} clusterMsgDataGossip;

clusterMsgDataGossip 結構結構涵蓋三種包格式:
1)ping 包格式
ping 包是一個心跳包,是 redis 集羣中每個節點通過心跳包可以知道其他節點的當前狀態並且保存到本節點狀態中。

2)pong 包格式
pong 包是接收到 ping 包或者是 meet 包之後作爲回覆包類型。當進行主從切換之後,新的主節點會向集羣中的所有節點直接發送一個 pong 包,通知從切換後節點角色的轉換。

3)meet 包格式
當執行 cluster meet 命令之後,執行端會向 ip:port 指定的地址發送 meet 包。

1.5 clusterMsgDataFail 結構:

typedef struct {
    char nodename[CLUSTER_NAMELEN];  //節點名稱
} clusterMsgDataFail;

clusterMsgDataFail 用於 fail 包,fail 包用來通知集羣中某個節點處於故障狀態。

1.6 clusterMsgDataPublish 結構:

typedef struct {
    uint32_t channel_len;        //渠道名稱長度
    uint32_t message_len;        //消息長度
    unsigned char bulk_data[8];  //渠道和消息內容
} clusterMsgDataPublish;

clusterMsgDataPublish 結構用於發佈 / 訂閱包使用。當向集羣中任意一個節點發送 publish 信息後,該節點會向集羣中所有節點廣播一條 publish 包。

1.7 clusterMsgDataUpdate 結構:

typedef struct {
    uint64_t configEpoch;                 /* 配置紀元. */
    char nodename[CLUSTER_NAMELEN];       /* 節點名稱. */
    unsigned char slots[CLUSTER_SLOTS/8]; /* 服務的slots */
} clusterMsgDataUpdate;

clusterMsgDataUpdate 結構主要用於 update 包,update 包用於更新集羣節點中的配置。

(二) 數據遷移

2.1 操作數據遷移命令

#將本節點的槽 slot 遷移到 node_id 指定的節點中
CLUSTER SETSLOT <slot> MIGRATING <node_id>

#從 node_id 指定的節點中導入槽 slot 到本節點
CLUSTER SETSLOT <slot> IMPORTING <node_id>

2.2 數據遷移原理

如果 A 節點操作 CLUSTER SETSLOT 命令遷移 10000 槽到 B 節點,此時 A、B 兩個節點都會存在 10000 槽。

typedef struct clusterState {
    //...省略
    clusterNode *migrating_slots_to[CLUSTER_SLOTS];   //遷移槽列表
    clusterNode *importing_slots_from[CLUSTER_SLOTS]; //導入槽列表
    clusterNode *slots[CLUSTER_SLOTS];                //當前槽列表
    //...省略
}

clusterState 中分別存放三種槽的列表:遷移槽列表、導入槽列表、當前槽列表。

2.3 數據遷移源碼分析

void clusterCommand(client *c) {
    //。。。省略
    int slot;
    clusterNode *n;

    if (nodeIsSlave(myself)) { //判斷是否爲從
        addReplyError(c,"Please use SETSLOT only with masters.");
        return;
    }
    
    if ((slot = getSlotOrReply(c,c->argv[2])) == -1) return; //獲得槽,沒有則返回
    
    if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) {
        if (server.cluster->slots[slot] != myself) { //判讀槽必須是當前節點
            addReplyErrorFormat(c,"I'm not the owner of hash slot %u",slot);
            return;
        }
        if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) { //獲取槽
            addReplyErrorFormat(c,"I don't know about node %s",
                (char*)c->argv[4]->ptr);
            return;
        }
        server.cluster->migrating_slots_to[slot] = n;  //設置遷移槽
    } else if (!strcasecmp(c->argv[3]->ptr,"importing") && c->argc == 5) {
        if (server.cluster->slots[slot] == myself) { //判讀槽不能是當前節點
            addReplyErrorFormat(c,
                "I'm already the owner of hash slot %u",slot);
            return;
        }
        if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
            addReplyErrorFormat(c,"I don't know about node %s",
                (char*)c->argv[3]->ptr);
            return;
        }
        server.cluster->importing_slots_from[slot] = n;  //設置倒入槽
    } 
}

migrating:滿足條件
1)必須是 master;
2)必須槽爲 0 ~ 16383 數字;
3)必須槽是本節點的;

importing:滿足條件
1)必須是 master;
2)必須槽爲 0 ~ 16383 數字;
3)必須槽不是本節點的;

總結:

1.redis4.0 中有 9 種消息包類型,redis5.0 種有 10 種消息包類型。其中涉及故障轉移的三種包狀態是沒有包結構的。
2. 消息包類型是通過 clusterMsg 結構中的 type 去映射不同的 data 包結構體。由於 data 對應的 clusterMsgData 結構體是一個 union 結構。
3. migrating 遷移槽時,需滿足幾個條件。必須是 master,必須槽是本節點的。
4. importing 倒入槽時,需滿足幾個條件。必須是 master,必須槽是其他節點的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JDle7Q90hk8i7Dw_2rkFAA