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之間數據的同步將在其它文章中介紹。數組
Redis中存在多個master,每一個master又能夠有多個slave,現假設有集羣中有9個節點,3個master,每一個master節點又有2個slave,那麼它的結構能夠表示如圖1-1:緩存
圖1-1 集羣結構圖數據結構
不一樣的master能夠擁有不一樣數量的slave,且集羣中任意一個節點都與其它全部節點創建了鏈接,每個節點都在稱爲cluster port的端口監聽其它節點的集羣通訊鏈接。併發
與集羣相關的數據結構主要有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),相似於心跳信息,維護整個集羣的狀態。
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指向它。方便修改自身節點的狀態。
每個節點都會在cluster port端口監聽tcp鏈接請求,參見clusterInit函數,而且每一個節點都有一個定時任務clusterCron,其中會遍歷nodes字典,檢測其中的clusterNode的link是否創建,若是沒有創建鏈接,那麼會主動鏈接該clusterNode所表明的節點創建鏈接。若是nodes字典中沒有某個節點clusterNode結構,那麼便不會與它創建鏈接。
創建clusterNode的時機大體有以下幾處:
新創建的clusterNode的nameid是隨機的,而且此時的clusterNode中flag設置爲CLUSTER_NODE_HANDSHAKE狀態,表示還沒有首次通訊。當clusterCron中創建相應的link,併發送ping/meet消息,收到響應消息(Pong)時去除CLUSTER_NODE_HANDSHAKE狀態,並將clusterNode的nameid修改成響應消息中附帶的nameid,至此成功創建一個方向的鏈接,反方向的鏈接由對方主動發起創建。
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與數據包處理邏輯分離,簡化代碼結構。