redis源碼分析(五)--cluster(集羣)結構

Redis集羣

  Redis支持集羣模式,集羣中能夠存在多個master,每一個master又能夠擁有多個slave。數據根據關鍵字映射到不一樣的slot,每個master負責一部分的slots,數據被存儲在負責它的slot的master節點上。slave會同步它的master節點上的數據到本節點,當master節點掛掉時,slave能夠上升爲master節點繼續服務,保障集羣的完整性與可靠性。node

  Redis集羣中的每個節點都擁有其它全部節點的信息,任意節點都知道客戶端請求的數據被存儲在哪個master節點上,所以客戶端能夠鏈接到任意節點執行操做。這不一樣於hdfs文件系統的namenode與datanode的概念,namenode一旦掛掉,整個系統就不可用了,namenode成爲了系統的瓶頸。Redis中master不只擁有多個slave,而且空閒的slave還能夠在不一樣的master之間流動,增強集羣的可靠性。redis

  這篇文章主要介紹redis的集羣結構及內部集羣信息的管理,slave與master之間數據的同步將在其它文章中介紹。數組

1. Redis集羣結構

  Redis中存在多個master,每一個master又能夠有多個slave,現假設有集羣中有9個節點,3個master,每一個master節點又有2個slave,那麼它的結構能夠表示如圖1-1:緩存

 

圖1-1 集羣結構圖數據結構

不一樣的master能夠擁有不一樣數量的slave,且集羣中任意一個節點都與其它全部節點創建了鏈接,每個節點都在稱爲cluster port的端口監聽其它節點的集羣通訊鏈接。併發

2. Redis集羣數據結構

  與集羣相關的數據結構主要有clusterState與clusterNode兩個(在cluster.h源文件中聲明):每個redis實例擁有惟一一個clusterState實例,即server.cluster;而clusterNode的實例與集羣中的節點數目n對應,每個節點上都擁有n個clusterNode實例表示它知道的n個節點的信息,存儲在clusterState結構的nodes成員中。socket

  clusterState中有2個與集羣結構相關的成員,聲明以下(省略了大部分紅員,僅留下了與集羣總體結構相關的成員):tcp

typedef struct clusterState {
    clusterNode *myself;  /* This node */
  ...
    dict *nodes;          /* Hash table of name -> clusterNode structures */
  ...
    clusterNode *slots[CLUSTER_SLOTS];
  ...
} clusterState;

nodes成員是一個字典,以節點nameid爲關鍵字,指向clusterNode的指針爲值,假設集羣有4個節點,那麼nodes存儲內容大體能夠表示以下:函數

 

圖2-1 nodes結構this

  集羣中的節點都有一個nameid,以nameid爲索引便可在nodes字典中找到描述該節點信息的clusterNode實例。而slots是一個clusterNode結構的指針數組,CLUSTER_SLOTS是redis中定義的支持的slot最大值,全部的key計算獲得的slot都小於該值。slots[slot]存儲着負責該slot的master節點的clusterNode結構的指針。每個節點上都擁有該slots數組,所以在任意節點上均可以查找到負責某個slot的主節點的信息。假設集羣擁有3個master節點,那麼slots結構可表示以下:

 

圖2-2 clusterState中的指針數組slots

  clusterNode結構描述了一個節點的基本信息,如ip,port, cluster port等,其聲明以下:

typedef struct clusterNode {
    mstime_t ctime; /* Node object creation time. */
    char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
    int flags;      /* CLUSTER_NODE_... */
  ...
    unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots;   /* Number of slots handled by this node */
    int numslaves;  /* Number of slave nodes, if this is a master */
    struct clusterNode **slaves; /* pointers to slave nodes */
    struct clusterNode *slaveof; /* pointer to the master node. Note that it
                                    may be NULL even if the node is a slave
                                    if we don't have the master node in our
                                    tables. */
  ...
    char ip[NET_IP_STR_LEN];  /* Latest known IP address of this node */
    int port;                   /* Latest known clients port of this node */
    int cport;                  /* Latest known cluster port of this node. */
    clusterLink *link;          /* TCP/IP link with this node */
  ...
} clusterNode;

name便是nodes字典中用做關鍵字的節點nameid

slots與clusterState中的slots有所不一樣,這裏以bit的索引做爲slot值,以該bit的狀態標識該clusterNode對應的節點是否負責該slot。

slaves與slaveof表明了節點之間的master-slave關係。若是這是一個master節點,那麼它的slave節點的clusterNode指針存儲在slaves數組中;若是這是一個slave節點,那麼slaveof指向了它的master節點的clusterNode。

link,指向當前節點與該clusterNode表明的節點之間的鏈接的相關信息,節點之間經過該link按期發送ping/pong消息。

 

  在每個節點中都維護了它知道的全部節點的clusterNode結構,從集羣角度上來說全部節點地位都是平等的,避免了瓶頸節點的出現。而這些結構中的信息主要用於節點之間的通訊(即ping/pong),相似於心跳信息,維護整個集羣的狀態。

3. Redis集羣通訊結構

  Redis集羣中的節點沒有namenode與datanode的區別,每個節點都維護了全部節點的信息。如前一小節介紹,clusterNode結構中的link指向了當前節點與clusterNode所表明的節點之間的鏈接,Redis中每個節點都與它所知道的全部節點之間維護了一個鏈接,經過這些鏈接發ping/pong消息,同步集羣信息。集羣中任意兩個節點之間都創建了兩個tcp鏈接,例若有nodeA與nodeB,那麼nodeA中表明nodeB的clusterNode中有一個link維護了A主動與B創建的鏈接,而nodeB中表明nodeA的clusterNode中也有一個link維護了B主動與A創建的鏈接,即構建了一個全雙工的通訊鏈路。假設集羣中存在3個節點,那麼它們之間的通訊結構以下所示:

 

圖3-1 3個節點的集羣通訊結構

注意每一個節點也維護了自身的clusterNode結構,而且在clusterState中使用myself指向它。方便修改自身節點的狀態。

4. link的創建與節點發現

  每個節點都會在cluster port端口監聽tcp鏈接請求,參見clusterInit函數,而且每一個節點都有一個定時任務clusterCron,其中會遍歷nodes字典,檢測其中的clusterNode的link是否創建,若是沒有創建鏈接,那麼會主動鏈接該clusterNode所表明的節點創建鏈接。若是nodes字典中沒有某個節點clusterNode結構,那麼便不會與它創建鏈接。

  創建clusterNode的時機大體有以下幾處:

  1. 從文件中加載節點信息創建 clusterNode結構,在函數clusterLoadConfig中。
  2. 客戶端執行meet命令告知節點信息,創建相應的clusterNode結構,由函數clusterCommand調用clusterStartHandshake完成。
  3. 接收到meet類消息,創建與發送方對應的clusterNode結構,在函數clusterProcessPacket中。
  4. 接收到的ping/pong/meet消息中帶有其它不知道的節點信息,創建相應的clusterNode結構,一樣在clusterProcessPacket函數中,調用clusterStartHandshake完成。

  新創建的clusterNode的nameid是隨機的,而且此時的clusterNode中flag設置爲CLUSTER_NODE_HANDSHAKE狀態,表示還沒有首次通訊。當clusterCron中創建相應的link,併發送ping/meet消息,收到響應消息(Pong)時去除CLUSTER_NODE_HANDSHAKE狀態,並將clusterNode的nameid修改成響應消息中附帶的nameid,至此成功創建一個方向的鏈接,反方向的鏈接由對方主動發起創建。 

5. Redis處理通訊的函數結構

  1. clusterInit中監聽端口cport,註冊讀事件,響應函數爲clusterAcceptHandler。
  2. clusterCron中主動創建鏈接,並將鏈接結構保存到clusterNode中的link指針中。註冊讀事件,響應函數爲clusterReadHandler,並主動發送ping/meet消息,若先前未註冊寫事件,則爲該link註冊寫事件,響應函數爲clusterWriteHandler。
  3. clusterAcceptHandler中接受鏈接後創建link結構(未保存),並註冊讀事件,響應函數爲clusterReadHandler。
  4.  clusterReadHandler中接收數據,當接收到一個完整的消息後,調用clusterProcessPacket函數處理。

Redis中定義了集羣通訊消息的結構,每個消息至少包含一個消息頭,而消息頭中包含整個消息的長度,所以clusterReadHandler中能夠判斷是否接收到一個完整的數據包。

  link指向的結構中包含了sndbuf與rcvbuf兩個緩存,其定義以下: 

/* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink {
    mstime_t ctime;             /* Link creation time */
    int fd;                     /* TCP socket file descriptor */
    sds sndbuf;                 /* Packet send buffer */
    sds rcvbuf;                 /* Packet reception buffer */
    struct clusterNode *node;   /* Node related to this link if any, or NULL */
} clusterLink;

clusterWriteHandler負責將sndbuf中的數據發送出去,clusterReadHandler負責將數據接收到rcvbuf中,須要發送的集羣數據都先填充到sndbuf中,須要接收到數據都先緩存到rcvbuf中,rcvbuf中積累了一個完整數據包再由clusterProcessPacket函數處理。經過緩存將io與數據包處理邏輯分離,簡化代碼結構。

相關文章
相關標籤/搜索