複製和故障轉移 Redis集群中的節點分為主節點(master)和從節點(slave),其中主節點用於處理槽,而從節點則用於複製某個主節點,併在被覆制 的主節點下線時,代替下線主節點繼續處理命令請求。 設置從節點:CLUSTER REPLICATE < node_id >可以讓接收命令的節點稱為no ...
複製和故障轉移
Redis集群中的節點分為主節點(master)和從節點(slave),其中主節點用於處理槽,而從節點則用於複製某個主節點,併在被覆制 的主節點下線時,代替下線主節點繼續處理命令請求。
設置從節點:CLUSTER REPLICATE < node_id >可以讓接收命令的節點稱為node_id 所指定節點的從節點,並開始對主節點進行複製。
1)接收到該命令的節點首先會在自己的clusterState.nodes字典中找到node_id所對應節點的clusterNode結構,並將自己的clusterState.myself.slaveof指針指向這個結構,以此來記錄這個節點正在複製的主節點:
struct clusterNode{ //如果這個時一個從節點,那麼指向主節點 struct clusterNode *slaveof; }
2)節點修改自己的clusterState.myself.flags中的屬性,關閉原本的REDIS_NODE_MASTER標識,打開REDIS_NODE_SLAVE標識,標識這個節點已經由原來的主節點變成了從節點。
3)節點會調用複製代碼,根據clusterState.myself.slaveof指向clusterNode結構所保存的IP地址和埠號,對節點進行複製。
一個節點稱為從節點,並開始複製某個主節點這一信息會通過消息發送給集群中的其他節點,最終集群中的所有節點都會知道某個從節點正在複製某個主節點。
集群中的所有節點都會在代表主節點的clusterNode結構的slaves屬性和numslaves屬性中記錄正在複製這個主節點的從節點名單:
struct clusterNode{ //正在複製這個主節點的從節點數量 int numslaves; //數組,每個數組項指向一個正在複製這個主節點的從節點的clusterNode struct clusterNode **slaves; }
集群中的每個節點都會定期地向集群中的其他節點發送PING消息,一次來檢測對方是否線上,如果接收PING消息的節點沒有在規定的時間內,向發送PING消息的節點返回PONG消息,那麼發送PING消息的節點就會將階段後PING消息的節點標記為疑似下線(PFAIL)。
集群中的各個節點會通過相互發送消息的方式來交換集群中各個節點的狀態信息:某個節點處於線上狀態、疑似下線、已下線狀態。
當一個主節點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設計與實現》,僅在此做學習與大家分享。