Redis 節(jié)點(diǎn)

2018-08-02 14:56 更新

一個(gè) Redis 集群通常由多個(gè)節(jié)點(diǎn)(node)組成, 在剛開(kāi)始的時(shí)候, 每個(gè)節(jié)點(diǎn)都是相互獨(dú)立的, 它們都處于一個(gè)只包含自己的集群當(dāng)中, 要組建一個(gè)真正可工作的集群, 我們必須將各個(gè)獨(dú)立的節(jié)點(diǎn)連接起來(lái), 構(gòu)成一個(gè)包含多個(gè)節(jié)點(diǎn)的集群。

連接各個(gè)節(jié)點(diǎn)的工作可以使用 CLUSTER MEET 命令來(lái)完成, 該命令的格式如下:

CLUSTER MEET <ip> <port>

向一個(gè)節(jié)點(diǎn) node 發(fā)送 CLUSTER MEET 命令, 可以讓 node 節(jié)點(diǎn)與 ip 和 port 所指定的節(jié)點(diǎn)進(jìn)行握手(handshake), 當(dāng)握手成功時(shí), node節(jié)點(diǎn)就會(huì)將 ip 和 port 所指定的節(jié)點(diǎn)添加到 node 節(jié)點(diǎn)當(dāng)前所在的集群中。

舉個(gè)例子, 假設(shè)現(xiàn)在有三個(gè)獨(dú)立的節(jié)點(diǎn) 127.0.0.1:7000 、 127.0.0.1:7001 、 127.0.0.1:7002 (下文省略 IP 地址,直接使用端口號(hào)來(lái)區(qū)分各個(gè)節(jié)點(diǎn)), 我們首先使用客戶端連上節(jié)點(diǎn) 7000 , 通過(guò)發(fā)送 CLUSTER NODE 命令可以看到, 集群目前只包含 7000 自己一個(gè)節(jié)點(diǎn):

$ redis-cli -c -p 7000
127.0.0.1:7000> CLUSTER NODES
51549e625cfda318ad27423a31e7476fe3cd2939 :0 myself,master - 0 0 0 connected

通過(guò)向節(jié)點(diǎn) 7000 發(fā)送以下命令, 我們可以將節(jié)點(diǎn) 7001 添加到節(jié)點(diǎn) 7000 所在的集群里面:

127.0.0.1:7000> CLUSTER MEET 127.0.0.1 7001
OK

127.0.0.1:7000> CLUSTER NODES
68eef66df23420a5862208ef5b1a7005b806f2ff 127.0.0.1:7001 master - 0 1388204746210 0 connected
51549e625cfda318ad27423a31e7476fe3cd2939 :0 myself,master - 0 0 0 connected

繼續(xù)向節(jié)點(diǎn) 7000 發(fā)送以下命令, 我們可以將節(jié)點(diǎn) 7002 也添加到節(jié)點(diǎn) 7000 和節(jié)點(diǎn) 7001 所在的集群里面:

127.0.0.1:7000> CLUSTER MEET 127.0.0.1 7002
OK

127.0.0.1:7000> CLUSTER NODES
68eef66df23420a5862208ef5b1a7005b806f2ff 127.0.0.1:7001 master - 0 1388204848376 0 connected
9dfb4c4e016e627d9769e4c9bb0d4fa208e65c26 127.0.0.1:7002 master - 0 1388204847977 0 connected
51549e625cfda318ad27423a31e7476fe3cd2939 :0 myself,master - 0 0 0 connected

現(xiàn)在, 這個(gè)集群里面包含了 7000 、 7001 和 7002 三個(gè)節(jié)點(diǎn), 圖 IMAGE_CONNECT_NODES_1 至 IMAGE_CONNECT_NODES_5 展示了這三個(gè)節(jié)點(diǎn)進(jìn)行握手的整個(gè)過(guò)程。

本節(jié)接下來(lái)的內(nèi)容將介紹啟動(dòng)節(jié)點(diǎn)的方法, 和集群有關(guān)的數(shù)據(jù)結(jié)構(gòu), 以及 CLUSTER MEET 命令的實(shí)現(xiàn)原理。

啟動(dòng)節(jié)點(diǎn)

一個(gè)節(jié)點(diǎn)就是一個(gè)運(yùn)行在集群模式下的 Redis 服務(wù)器, Redis 服務(wù)器在啟動(dòng)時(shí)會(huì)根據(jù) cluster-enabled 配置選項(xiàng)的是否為 yes 來(lái)決定是否開(kāi)啟服務(wù)器的集群模式, 如圖 IMAGE_NODE_OR_SERVER 所示。

節(jié)點(diǎn)(運(yùn)行在集群模式下的 Redis 服務(wù)器)會(huì)繼續(xù)使用所有在單機(jī)模式中使用的服務(wù)器組件, 比如說(shuō):

  • 節(jié)點(diǎn)會(huì)繼續(xù)使用文件事件處理器來(lái)處理命令請(qǐng)求和返回命令回復(fù)。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用時(shí)間事件處理器來(lái)執(zhí)行 serverCron 函數(shù), 而 serverCron 函數(shù)又會(huì)調(diào)用集群模式特有的 clusterCron 函數(shù): clusterCron函數(shù)負(fù)責(zé)執(zhí)行在集群模式下需要執(zhí)行的常規(guī)操作, 比如向集群中的其他節(jié)點(diǎn)發(fā)送 Gossip 消息, 檢查節(jié)點(diǎn)是否斷線; 又或者檢查是否需要對(duì)下線節(jié)點(diǎn)進(jìn)行自動(dòng)故障轉(zhuǎn)移, 等等。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用數(shù)據(jù)庫(kù)來(lái)保存鍵值對(duì)數(shù)據(jù),鍵值對(duì)依然會(huì)是各種不同類(lèi)型的對(duì)象。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用 RDB 持久化模塊和 AOF 持久化模塊來(lái)執(zhí)行持久化工作。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用發(fā)布與訂閱模塊來(lái)執(zhí)行 PUBLISH 、 SUBSCRIBE 等命令。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用復(fù)制模塊來(lái)進(jìn)行節(jié)點(diǎn)的復(fù)制工作。
  • 節(jié)點(diǎn)會(huì)繼續(xù)使用 Lua 腳本環(huán)境來(lái)執(zhí)行客戶端輸入的 Lua 腳本。

諸如此類(lèi)。

除此之外, 節(jié)點(diǎn)會(huì)繼續(xù)使用 redisServer 結(jié)構(gòu)來(lái)保存服務(wù)器的狀態(tài), 使用 redisClient 結(jié)構(gòu)來(lái)保存客戶端的狀態(tài), 至于那些只有在集群模式下才會(huì)用到的數(shù)據(jù), 節(jié)點(diǎn)將它們保存到了 cluster.h/clusterNode 結(jié)構(gòu), cluster.h/clusterLink 結(jié)構(gòu), 以及 cluster.h/clusterState 結(jié)構(gòu)里面, 接下來(lái)的一節(jié)將對(duì)這三種數(shù)據(jù)結(jié)構(gòu)進(jìn)行介紹。

集群數(shù)據(jù)結(jié)構(gòu)

clusterNode 結(jié)構(gòu)保存了一個(gè)節(jié)點(diǎn)的當(dāng)前狀態(tài), 比如節(jié)點(diǎn)的創(chuàng)建時(shí)間, 節(jié)點(diǎn)的名字, 節(jié)點(diǎn)當(dāng)前的配置紀(jì)元, 節(jié)點(diǎn)的 IP 和地址, 等等。

每個(gè)節(jié)點(diǎn)都會(huì)使用一個(gè) clusterNode 結(jié)構(gòu)來(lái)記錄自己的狀態(tài), 并為集群中的所有其他節(jié)點(diǎn)(包括主節(jié)點(diǎn)和從節(jié)點(diǎn))都創(chuàng)建一個(gè)相應(yīng)的clusterNode 結(jié)構(gòu), 以此來(lái)記錄其他節(jié)點(diǎn)的狀態(tài):

struct clusterNode {

    // 創(chuàng)建節(jié)點(diǎn)的時(shí)間
    mstime_t ctime;

    // 節(jié)點(diǎn)的名字,由 40 個(gè)十六進(jìn)制字符組成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    char name[REDIS_CLUSTER_NAMELEN];

    // 節(jié)點(diǎn)標(biāo)識(shí)
    // 使用各種不同的標(biāo)識(shí)值記錄節(jié)點(diǎn)的角色(比如主節(jié)點(diǎn)或者從節(jié)點(diǎn)),
    // 以及節(jié)點(diǎn)目前所處的狀態(tài)(比如在線或者下線)。
    int flags;

    // 節(jié)點(diǎn)當(dāng)前的配置紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移
    uint64_t configEpoch;

    // 節(jié)點(diǎn)的 IP 地址
    char ip[REDIS_IP_STR_LEN];

    // 節(jié)點(diǎn)的端口號(hào)
    int port;

    // 保存連接節(jié)點(diǎn)所需的有關(guān)信息
    clusterLink *link;

    // ...

};

clusterNode 結(jié)構(gòu)的 link 屬性是一個(gè) clusterLink 結(jié)構(gòu), 該結(jié)構(gòu)保存了連接節(jié)點(diǎn)所需的有關(guān)信息, 比如套接字描述符, 輸入緩沖區(qū)和輸出緩沖區(qū):

typedef struct clusterLink {

    // 連接的創(chuàng)建時(shí)間
    mstime_t ctime;

    // TCP 套接字描述符
    int fd;

    // 輸出緩沖區(qū),保存著等待發(fā)送給其他節(jié)點(diǎn)的消息(message)。
    sds sndbuf;

    // 輸入緩沖區(qū),保存著從其他節(jié)點(diǎn)接收到的消息。
    sds rcvbuf;

    // 與這個(gè)連接相關(guān)聯(lián)的節(jié)點(diǎn),如果沒(méi)有的話就為 NULL
    struct clusterNode *node;

} clusterLink;

redisClient 結(jié)構(gòu)和 clusterLink 結(jié)構(gòu)的相同和不同之處

redisClient 結(jié)構(gòu)和 clusterLink 結(jié)構(gòu)都有自己的套接字描述符和輸入、輸出緩沖區(qū), 這兩個(gè)結(jié)構(gòu)的區(qū)別在于, redisClient 結(jié)構(gòu)中的套接字和緩沖區(qū)是用于連接客戶端的, 而 clusterLink 結(jié)構(gòu)中的套接字和緩沖區(qū)則是用于連接節(jié)點(diǎn)的。

最后, 每個(gè)節(jié)點(diǎn)都保存著一個(gè) clusterState 結(jié)構(gòu), 這個(gè)結(jié)構(gòu)記錄了在當(dāng)前節(jié)點(diǎn)的視角下, 集群目前所處的狀態(tài) —— 比如集群是在線還是下線, 集群包含多少個(gè)節(jié)點(diǎn), 集群當(dāng)前的配置紀(jì)元, 諸如此類(lèi):

typedef struct clusterState {

    // 指向當(dāng)前節(jié)點(diǎn)的指針
    clusterNode *myself;

    // 集群當(dāng)前的配置紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移
    uint64_t currentEpoch;

    // 集群當(dāng)前的狀態(tài):是在線還是下線
    int state;

    // 集群中至少處理著一個(gè)槽的節(jié)點(diǎn)的數(shù)量
    int size;

    // 集群節(jié)點(diǎn)名單(包括 myself 節(jié)點(diǎn))
    // 字典的鍵為節(jié)點(diǎn)的名字,字典的值為節(jié)點(diǎn)對(duì)應(yīng)的 clusterNode 結(jié)構(gòu)
    dict *nodes;

    // ...

} clusterState;

以前面介紹的 7000 、 7001 、 7002 三個(gè)節(jié)點(diǎn)為例, 圖 IMAGE_CLUSTER_STATE_OF_7000 展示了節(jié)點(diǎn) 7000 創(chuàng)建的 clusterState 結(jié)構(gòu), 這個(gè)結(jié)構(gòu)從節(jié)點(diǎn) 7000 的角度記錄了集群、以及集群包含的三個(gè)節(jié)點(diǎn)的當(dāng)前狀態(tài) (為了空間考慮,圖中省略了 clusterNode 結(jié)構(gòu)的一部分屬性):

  • 結(jié)構(gòu)的 currentEpoch 屬性的值為 0 , 表示集群當(dāng)前的配置紀(jì)元為 0 。
  • 結(jié)構(gòu)的 size 屬性的值為 0 , 表示集群目前沒(méi)有任何節(jié)點(diǎn)在處理槽: 因此結(jié)構(gòu)的 state 屬性的值為 REDIS_CLUSTER_FAIL —— 這表示集群目前處于下線狀態(tài)。
  • 結(jié)構(gòu)的 nodes 字典記錄了集群目前包含的三個(gè)節(jié)點(diǎn), 這三個(gè)節(jié)點(diǎn)分別由三個(gè) clusterNode 結(jié)構(gòu)表示: 其中 myself 指針指向代表節(jié)點(diǎn) 7000 的 clusterNode 結(jié)構(gòu), 而字典中的另外兩個(gè)指針則分別指向代表節(jié)點(diǎn) 7001 和代表節(jié)點(diǎn) 7002 的 clusterNode 結(jié)構(gòu), 這兩個(gè)節(jié)點(diǎn)是節(jié)點(diǎn) 7000 已知的在集群中的其他節(jié)點(diǎn)。
  • 三個(gè)節(jié)點(diǎn)的 clusterNode 結(jié)構(gòu)的 flags 屬性都是 REDIS_NODE_MASTER ,說(shuō)明三個(gè)節(jié)點(diǎn)都是主節(jié)點(diǎn)。

節(jié)點(diǎn) 7001 和節(jié)點(diǎn) 7002 也會(huì)創(chuàng)建類(lèi)似的 clusterState 結(jié)構(gòu):

  • 不過(guò)在節(jié)點(diǎn) 7001 創(chuàng)建的 clusterState 結(jié)構(gòu)中, myself 指針將指向代表節(jié)點(diǎn) 7001 的 clusterNode 結(jié)構(gòu), 而節(jié)點(diǎn) 7000 和節(jié)點(diǎn) 7002 則是集群中的其他節(jié)點(diǎn)。
  • 而在節(jié)點(diǎn) 7002 創(chuàng)建的 clusterState 結(jié)構(gòu)中, myself 指針將指向代表節(jié)點(diǎn) 7002 的 clusterNode 結(jié)構(gòu), 而節(jié)點(diǎn) 7000 和節(jié)點(diǎn) 7001 則是集群中的其他節(jié)點(diǎn)。

CLUSTER MEET 命令的實(shí)現(xiàn)

通過(guò)向節(jié)點(diǎn) A 發(fā)送 CLUSTER MEET 命令, 客戶端可以讓接收命令的節(jié)點(diǎn) A 將另一個(gè)節(jié)點(diǎn) B 添加到節(jié)點(diǎn) A 當(dāng)前所在的集群里面:

CLUSTER MEET <ip> <port>

收到命令的節(jié)點(diǎn) A 將與節(jié)點(diǎn) B 進(jìn)行握手(handshake), 以此來(lái)確認(rèn)彼此的存在, 并為將來(lái)的進(jìn)一步通信打好基礎(chǔ):

  1. 節(jié)點(diǎn) A 會(huì)為節(jié)點(diǎn) B 創(chuàng)建一個(gè) clusterNode 結(jié)構(gòu), 并將該結(jié)構(gòu)添加到自己的 clusterState.nodes 字典里面。
  2. 之后, 節(jié)點(diǎn) A 將根據(jù) CLUSTER MEET 命令給定的 IP 地址和端口號(hào), 向節(jié)點(diǎn) B 發(fā)送一條 MEET 消息(message)。
  3. 如果一切順利, 節(jié)點(diǎn) B 將接收到節(jié)點(diǎn) A 發(fā)送的 MEET 消息, 節(jié)點(diǎn) B 會(huì)為節(jié)點(diǎn) A 創(chuàng)建一個(gè) clusterNode 結(jié)構(gòu), 并將該結(jié)構(gòu)添加到自己的 clusterState.nodes 字典里面。
  4. 之后, 節(jié)點(diǎn) B 將向節(jié)點(diǎn) A 返回一條 PONG 消息。
  5. 如果一切順利, 節(jié)點(diǎn) A 將接收到節(jié)點(diǎn) B 返回的 PONG 消息, 通過(guò)這條 PONG 消息節(jié)點(diǎn) A 可以知道節(jié)點(diǎn) B 已經(jīng)成功地接收到了自己發(fā)送的 MEET 消息。
  6. 之后, 節(jié)點(diǎn) A 將向節(jié)點(diǎn) B 返回一條 PING 消息。
  7. 如果一切順利, 節(jié)點(diǎn) B 將接收到節(jié)點(diǎn) A 返回的 PING 消息, 通過(guò)這條 PING 消息節(jié)點(diǎn) B 可以知道節(jié)點(diǎn) A 已經(jīng)成功地接收到了自己返回的 PONG 消息, 握手完成。

圖 IMAGE_HANDSHAKE 展示了以上步驟描述的握手過(guò)程。

之后, 節(jié)點(diǎn) A 會(huì)將節(jié)點(diǎn) B 的信息通過(guò) Gossip 協(xié)議傳播給集群中的其他節(jié)點(diǎn), 讓其他節(jié)點(diǎn)也與節(jié)點(diǎn) B 進(jìn)行握手, 最終, 經(jīng)過(guò)一段時(shí)間之后, 節(jié)點(diǎn) B 會(huì)被集群中的所有節(jié)點(diǎn)認(rèn)識(shí)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)