複製和故障轉移node
Redis集羣中的節點分爲主節點(master)和從節點(slave),其中主節點用於處理槽,而從節點則用於複製某個主節點,並在被複制 的主節點下線時,代替下線主節點繼續處理命令請求。數組
設置從節點:CLUSTER REPLICATE < node_id >可讓接收命令的節點稱爲node_id 所指定節點的從節點,並開始對主節點進行復制。學習
1)接收到該命令的節點首先會在本身的clusterState.nodes字典中找到node_id所對應節點的clusterNode結構,並將本身的clusterState.myself.slaveof指針指向這個結構,以此來記錄這個節點正在複製的主節點:ui
struct clusterNode{ //若是這個時一個從節點,那麼指向主節點 struct clusterNode *slaveof; }
2)節點修改本身的clusterState.myself.flags中的屬性,關閉本來的REDIS_NODE_MASTER標識,打開REDIS_NODE_SLAVE標識,標識這個節點已經由原來的主節點變成了從節點。spa
3)節點會調用複製代碼,根據clusterState.myself.slaveof指向clusterNode結構所保存的IP地址和端口號,對節點進行復制。設計
一個節點稱爲從節點,並開始複製某個主節點這一信息會經過消息發送給集羣中的其餘節點,最終集羣中的全部節點都會知道某個從節點正在複製某個主節點。指針
集羣中的全部節點都會在表明主節點的clusterNode結構的slaves屬性和numslaves屬性中記錄正在複製這個主節點的從節點名單:code
struct clusterNode{ //正在複製這個主節點的從節點數量 int numslaves; //數組,每一個數組項指向一個正在複製這個主節點的從節點的clusterNode struct clusterNode **slaves; }
集羣中的每一個節點都會按期地向集羣中的其餘節點發送PING消息,一次來檢測對方是否在線,若是接收PING消息的節點沒有在規定的時間內,向發送PING消息的節點返回PONG消息,那麼發送PING消息的節點就會將階段後PING消息的節點標記爲疑似下線(PFAIL)。對象
集羣中的各個節點會經過相互發送消息的方式來交換集羣中各個節點的狀態信息:某個節點處於在線狀態、疑似下線、已下線狀態。blog
當一個主節點A經過罅隙得知主節點B認爲主節點C進入疑似下線狀態時,主節點A會在本身的clusterState.nodes字典中找到主節點C所對應的clusterNode結構,並將主節點B的下線報告添加到clusterNode結構的fail_reports鏈表中
status clusterNode{ list *fali_reports;//鏈表,記錄全部其餘節點對該節點的下線報告 };
下線報告結構:
struct c;isterNodeFailReport{ //報告目標節點已經下線的節點 struct clusterNode *node; //最後一個從node節點收到下線報告的時間(程序使用這個時間戳來檢查下線報告是否過時) mstime_t time; } typedef clusterNodeFailReport;
若是集羣裏半數以上負責處理槽的主節點都將某個主節點x報告未疑似下線,那麼這個主節點x將被標記未已下線,將主節點x標記爲已下線的節點會向集羣廣播一條關於主節點x的FAIL罅隙,全部收到這條罅隙的節點都會當即將主節點x標記爲已下線。
故障轉移的步驟:
1)複製下線主節點的全部從節點裏面,會有一個從節點被選中,
2)被選中的從節點會執行SLAVEOF no one命令,成爲新的主節點。
3)新的主節點會撤銷全部對已下線主節點的槽指派,並將這些槽指派給本身。
4)新的主節點向集羣廣播一條PONG消息,這條消息讓其餘集羣中的其餘節點當即知道這個節點已經由從節點變爲主節點,而且這個主節點已經接管了本來已下線節點負責處理的槽。
5)新的主節點開始接收和本身負責處理的槽有關的命令請求,故障轉移完成。
選舉新的主節點:
1)集羣的配置紀元是一個計數器。他的初始值爲0;
2)當集羣中的某個節點開始一次故障轉移操做時,集羣配置紀元的值會被加1。
3)集羣裏面每一個負責處理槽的主節點都有一次投票機會,而第一個向主節點要求投票的從節點將得到主節點的投票。
4)當從節點發現本身正在複製的主節點進入下線狀態時,從節點會向集羣官博一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求全部收到這個消息、而且具備投票權的主節點向這個從節點投票。
5)若是一個主節點具備投票權,而且這個主節點還沒有投票給其餘從節點,那麼主節點將向要求投票的從節點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支持從節點成爲新的主節點。
6)每一個參與選舉的從節點都會收到CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,並根據本身收到了多少條這種消息來統計本身得到了多少主節點支持。
7)若是集羣中有N個具備投票權的主節點,那麼當一個從節點大於等於N/2+1張支持票時,這個從節點就當選成爲新的主節點。
8)若是在一個配置紀元裏面沒有從節點收集到足夠多的支持票,那麼集羣進入下一個紀元,再次進行選舉,直到選出新的主節點爲止。
消息
集羣中各個節點經過發送和接收消息來進行通訊,咱們稱發送消息的節點爲發送者,接收消息的節點爲接收者:
1)MEET消息,當發送者接到客戶端發送的CLUSTER MEET命令時,發送者會向接收者發送MEET消息,請求接收者加入到發送者當前所處的集羣裏面。
2)PING消息,集羣裏面的每一個節點默認每隔一秒鐘就會從已知節點列表中隨機選出五個節點,而後對這五個節點中最長時間沒有發送過PING消息的節點發送PING消息,以此檢測被選中的節點是否在線。除此以外,若是節點A最後一次收到節點B發送的PONG消息的時間,距離當前時間已超過了節點A的cluster-node-timeout選項設置時長的一半,那麼節點A也會向節點B發送PING消息,這能夠防止節點A因長時間沒有隨機選中節點B做爲PING消息的發送對象而致使節點B的信息更新滯後。
3)PONG消息,當接收者收到發送者發來的MEET消息或者PING時,爲了向發送者確認這條MEET、PING消息已到達,接收者會向發送者返回一條PONG消息。另外,一個節點也能夠經過向集羣發送集羣廣播本身的PONG消息來讓集羣中的其餘節點當即刷新關於這個節點的認識。
4)FAIL消息,當一個主節點A判斷另外一個主節點B已經進入FAIL狀態時,節點A會會向集羣廣播一條關於節點B的FAIL消息,全部接收到這條消息的節點都會當即將節點B標記爲已下線。
5)PUBLISH消息,當節點接收到一個PUBLISH命令時,節點會執行這個命令,並向集羣廣播一條PUBLISH消息,全部接收到這條PUBLISH消息的節點都會執行相同的PUBLISH命令。
一條消息由消息頭(header)和消息正文(data組成)
消息頭:
typedef struct { //消息的長度(消息頭的長度和消息正文的長度) uint32_t totlen; //消息的類型 uint16_t type; //消息正文包含的節點信息數量 //只有發送MEET、PING、PONG這三種Gossip協議消息時使用 uint16_t count; //薩鬆這所處的配置紀元 uint64_t currentEpoch; //若是發送者是一個主節點,那麼這裏面記錄的時發送者的配置紀元 //若是發送者時一個從節點,那麼這裏面記錄的時發送者正在複製的主節點的配置紀元 uint64_t configEpoch; //發送者的名稱(ID) char sender[REDIS_CLUSTER_NAMELEN]; //發送者目前的槽指派信息 unsigned char myslots[REDIS_CLUSTER_SLOTS/8]; //若是發送者是一個從節點,記錄的是發送者正在複製的主節點的名稱 //若是發送者是一個主節點,那麼這裏記錄的是REDIS_NODE_NULL_NAME char slaveof[REDIS_CLUSTER_NAMELEN]; //發送者的端口號 uint16_t port; //發送者的標識值 uint16_t flags; //發送者所處集羣的狀態 unsigned char state; //消息正文 union clusterMsgData data; } clusterMsg;
clusterMsg.data 結構:
union clusterMsgData{ //MEET PING PONG 消息正文 struct{ //每條MEET PING PONG消息都包含兩個 clusterMsgDataGossip 結構 clusterMsgDataGossip gossip[1] } ping; //FAIL 消息正文 struct{ clusterMsgDataFail about; } fali; //PUBLISH消息正文 struct{ clusterMsgDataPublish msg; } publish; }
clusterMsgDataGossip結構記錄了選中節點的名字,發送者與被選中節點最後一次發送和接收PING消息和PONG消息的時間戳,被選中節點的IP地址和端口號,以及被選中節點的標識值:
typedef struct { //節點的名字 char nodename[REDIS_CLUSTER_NAMELEN]; //最後一次向該節點發送PING消息的時間戳 uint32_t ping_sent; //最後一次從該 節點接收到PONG消息的時間戳 uint32_t pong_received; //節點的IP地址 char ip[16]; //節點的端口號 uint16_t port; //節點的標識值 uint16_t flags; } clusterMsgDataGossip;
天天學一點,總會有收穫。
說明:尊重做者知識產權,文中內容參考《Redis設計與實現》,僅在此作學習與你們分享。