redis cluster(5)- 集羣原理

https://www.jianshu.com/p/0232236688c1

1、集羣架構


Redis集羣設計 總體架構 

在這個圖中,每一個藍色的圈都代表着一個redis的服務器節點。它們任何兩個節點之間都是相互連通的。客戶端可以與任何一個節點相連接,然後就可以訪問集羣中的任何一個節點。對其進行存取和其他操作。

集羣節點屬性 

集羣中每個Master node負責存儲數據、集羣狀態,包括slots與nodes對應關係。Master nodes能夠自動發現其他nodes,檢測failure節點,當某個Master節點失效時,集羣能將覈實的Slave提升爲Master。下圖是節點的關聯信息,節點定時會將這些信息發送給其他節點:

1fc2412b7429e4ab5d8704fcd39520815ea2727b 10.9.42.37:6103 master - 0 1494082584680 9 connected 10923-13652 
08e70bb3edd7d3cabda7a2ab220f2f3610db38cd 10.9.33.204:6202 slave ad1334bd09ee73fdeb7b8f16194550fc2bf3a038 0 1494082586686 8 connected 
edaafc250f616e9e12c5182f0322445ea9a89085 10.9.33.204:6203 slave 1fc2412b7429e4ab5d8704fcd39520815ea2727b 0 1494082586184 9 connected 
06cd6f24caf98a1c1df0862eadac2b05254f909d 10.9.33.204:6201 slave d458c22ccced2f29358b6e6814a206d08285374e 0 1494082584179 7 connected 
3892b7fb410a4d6339364dbdda2ebc666ffee843 10.9.42.37:6203 slave 73f7d44c03ada58bf5adaeb340359e2c043ecfa0 0 1494082582679 12 connected 
73f7d44c03ada58bf5adaeb340359e2c043ecfa0 10.9.33.204:6103 master - 0 1494082585181 3 connected 13653-16383 
4004a64211bea5050a8f46b8436564d40380cd60 10.9.33.204:6101 master - 0 1494082583678 1 connected 2731-5460 
d458c22ccced2f29358b6e6814a206d08285374e 10.9.42.37:6101 master - 0 1494082588189 7 connected 0-2730 
f8868d59c0f3d935d3dbe35601506039520f7107 10.9.42.37:6201 slave 4004a64211bea5050a8f46b8436564d40380cd60 0 1494082587187 10 connected 
45ba0d6fc3d48a43ff72e10bcc17d2d8b2592cdf 10.9.33.204:6102 master - 0 1494082583179 2 connected 8192-10922 
007d7e17bfd26a3c1e21992bb5b656a92eb65686 10.9.42.37:6202 slave 45ba0d6fc3d48a43ff72e10bcc17d2d8b2592cdf 0 1494082588189 11 connected 
ad1334bd09ee73fdeb7b8f16194550fc2bf3a038 10.9.42.37:6102 myself,master - 0 0 8 connected 5461-8191 

從左至右分別是: 

節點ID、IP地址和端口,節點角色標誌、最後發送ping時間、最後接收到pong時間、連接狀態、節點負責處理的hash slot。

集羣可以自動識別出ip/port的變化,並通過Gossip(最終一致性,分佈式服務數據同步算法)協議廣播給其他節點知道。Gossip也稱“病毒感染算法”、“謠言傳播算法”(附錄一)。

 

2、集羣通信


2.1 集羣發現:MEET

最開始時,每個Redis實例自己是一個集羣,我們通過cluster meet讓各個結點互相“握手”。這也是Redis Cluster目前的一個欠缺之處:缺少結點的自動發現功能。

需要組建一個真正的可工作的集羣,我們必須將各個獨立的節點連接起來,構成一個包含多個節點的集羣。

連接各個節點的工作使用CLUSTER MEET命令來完成。

CLUSTER MEET <ip> <port>

 

CLUSTER MEET命令實現:

1)節點A會爲節點B創建一個clusterNode結構,並將該結構添加到自己的clusterState.nodes字典裏面。

2)節點A根據CLUSTER MEET命令給定的IP地址和端口號,向節點B發送一條MEET消息。

3)節點B接收到節點A發送的MEET消息,節點B會爲節點A創建一個clusterNode結構,並將該結構添加到自己的clusterState.nodes字典裏面。

4)節點B向節點A返回一條PONG消息。

5)節點A將受到節點B返回的PONG消息,通過這條PONG消息節點A可以知道節點B已經成功的接收了自己發送的MEET消息。

6)之後,節點A將向節點B返回一條PING消息。

7)節點B將接收到的節點A返回的PING消息,通過這條PING消息節點B可以知道節點A已經成功的接收到了自己返回的PONG消息,握手完成。

8)之後,節點A會將節點B的信息通過Gossip協議傳播給集羣中的其他節點,讓其他節點也與節點B進行握手,最終,經過一段時間後,節點B會被集羣中的所有節點認識。

2.2集羣消息處理clusterProcessPacket

1)更新接收消息計數器

2)查找發送者節點並且不是handshake節點

3)更新自己的epoch和slave的offset信息

4)處理MEET消息,使加入集羣

5)從goosip中發現未知節點,發起handshake

6)對PING,MEET回覆PONG

7)根據收到的心跳信息更新自己clusterState中的master-slave,slots信息

8)對FAILOVER_AUTH_REQUEST消息,檢查並投票

9)處理FAIL,FAILOVER_AUTH_ACK,UPDATE信息

 

 

2.3定時任務clusterCron

 

定時任務clusterCron

1)對handshake節點建立Link,發送Ping或Meet

2)向隨機幾點發送Ping

3)如果是從查看是否需要做Failover

4)統計並決定是否進行slave的遷移,來平衡不同master的slave數

5)判斷所有pfail報告數是否過半數

 

2.4心跳數據

 

發送消息頭信息Header

1)所負責slots的信息

2)主從信息

3)ip port信息

4)狀態信息

發送其他節點Gossip信息

1)ping_sent, pong_received

2)ip, port信息

3)狀態信息,比如發送者認爲該節點已經不可達,會在狀態信息中標記其爲PFAIL或FAIL

clusterMsg結構的currentEpoch、sender、myslots等屬性記錄了發送者自身的節點信息,接收者會根據這些信息,在自己的clusterState.nodes字典裏找到發送者對應的clusterNode結構,並對結構進行更新。

Redis集羣中的各個節點通過Gossip協議來交換各自關於不同節點的狀態信息,其中Gossip協議由MEET、PING、PONG三種消息實現,這三種消息的正文都由兩個clusterMsgDataGossip結構組成。

每次發送MEET、PING、PONG消息時,發送者都從自己的已知節點列表中隨機選出兩個節點(可以是主節點或者從節點),並將這兩個被選中節點的信息分別保存到兩個結構中。

當接收者收到消息時,接收者會訪問消息正文中的兩個結構,並根據自己是否認識clusterMsgDataGossip結構中記錄的被選中節點進行操作:

1.如果被選中節點不存在於接收者的已知節點列表,那麼說明接收者是第一次接觸到被選中節點,接收者將根據結構中記錄的IP地址和端口號等信息,與被選擇節點進行握手。

2.如果被選中節點已經存在於接收者的已知節點列表,那麼說明接收者之前已經與被選中節點進行過接觸,接收者將根據clusterMsgDataGossip結構記錄的信息,對被選中節點對應的clusterNode結構進行更新。

2.5數據結構

 

clusterNode結構保存了一個節點的當前狀態,比如節點的創建時間,節點的名字,節點當前的配置紀元,節點的IP和地址,等等。

1)slots:位圖,由當前clusterNode負責的slot爲1

2)salve, slaveof:主從關係信息

3)ping_sent, pong_received:心跳包收發時間

4)clusterLink *link:節點間的連接

5)list *fail_reports:收到的節點不可達投票

 

clusterState結構記錄了在當前節點的集羣目前所處的狀態。

1)myself:指針指向自己的clusterNode

2)currentEpoch:當前節點的最大epoch,可能在心跳包的處理中更新

3)nodes:當前節點記錄的所有節點,爲clusterNode指針數組

4)slots:slot與clusterNode指針映射關係

5)migrating_slots_to,

importing_slots_from:記錄slots的遷移信息

6)failover_auth_time,failover_auth_count,failover_auth_sent,failover_auth_rank,

failover_auth_epoch:Failover相關信息

 

clusterLink結構保存了連接節點所需的有關信息,比如套接字描述符,輸入緩衝區和輸出緩衝區。

 

3、數據分佈及槽信息


3.1槽(slot)概念

Redis Cluster中有一個16384長度的槽的概念,他們的編號爲0、1、2、3……16382、16383。這個槽是一個虛擬的槽,並不是真正存在的。正常工作的時候,Redis Cluster中的每個Master節點都會負責一部分的槽,當有某個key被映射到某個Master負責的槽,那麼這個Master負責爲這個key提供服務,至於哪個Master節點負責哪個槽,這是可以由用戶指定的,也可以在初始化的時候自動生成(redis-trib.rb腳本)。這裏值得一提的是,在Redis Cluster中,只有Master才擁有槽的所有權,如果是某個Master的slave,這個slave只負責槽的使用,但是沒有所有權。

3.2數據分片

在Redis Cluster中,擁有16384個slot,這個數是固定的,存儲在Redis Cluster中的所有的鍵都會被映射到這些slot中。數據庫中的每個鍵都屬於這16384個哈希槽的其中一個,集羣使用公式CRC16(key) % 16384來計算鍵key屬於哪個槽,其中CRC16(key)語句用於計算鍵key的CRC16校驗和。集羣中的每個節點負責處理一部分哈希槽。

 

3.3節點的槽指派信息

clusterNode結構的slots屬性和numslot屬性記錄了節點負責處理那些槽:

struct clusterNode {

//…

unsignedchar slots[16384/8];

};

Slots屬性是一個二進制位數組(bit

array),這個數組的長度爲16384/8=2048個字節,共包含16384個二進制位。

Master節點用bit來標識對於某個槽自己是否擁有。比如對於編號爲1的槽,Master只要判斷序列的第二位(索引從0開始)是不是爲1即可。時間複雜度爲O(1)。

3.4集羣所有槽的指派信息

通過將所有槽的指派信息保存在clusterState.slots數組裏面,程序要檢查槽i是否已經被指派,又或者取得負責處理槽i的節點,只需要訪問clusterState.slots[i]的值即可,複雜度僅爲O(1)。

3.5請求重定向

 

由於每個節點只負責部分slot,以及slot可能從一個節點遷移到另一節點,造成客戶端有可能會向錯誤的節點發起請求。因此需要有一種機制來對其進行發現和修正,這就是請求重定向。有兩種不同的重定向場景:

a)MOVED錯誤

1.請求的key對應的槽不在該節點上,節點將查看自身內部所保存的哈希槽到節點ID的映射記錄,      節點回復一個MOVED錯誤。

2.需要客戶端進行再次重試。

 

b)ASK錯誤

1.請求的key對應的槽目前的狀態屬於MIGRATING狀態,並且當前節點找不到這個key了,節點回    復ASK錯誤。ASK會把對應槽的IMPORTING節點返回給你,告訴你去IMPORTING的節點嘗試找找。

2.客戶端進行重試首先發送ASKING命令,節點將爲客戶端設置一個一次性的標誌(flag),使得 客戶端可以執行一次針對IMPORTING狀態的槽的命令請求,然後再發送真正的命令請求。

3.不必更新客戶端所記錄的槽至節點的映射。

 

4、數據遷移


當槽x從Node A向Node B遷移時,Node A和Node B都會有這個槽x,Node A上槽x的狀態設置爲MIGRATING,Node B上槽x的狀態被設置爲IMPORTING。

MIGRATING狀態

1)如果key存在則成功處理

2)如果key不存在,則返回客戶端ASK,客戶端根據ASK首先發送ASKING命令到目標節點,然後發送請求的命令到目標節點

3)當key包含多個命令,

     a)如果都存在則成功處理

     b)如果都不存在,則返回客戶端ASK

     c)如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍後重試,這樣當所有的        key都遷移完畢的時候客戶端重試請求的時候回得到ASK,然後經過一次重定向就           可以獲取這批鍵

4)此時不刷新客戶端中node的映射關係

IMPORTING狀態

1)如果key不在該節點上,會被MOVED重定向,刷新客戶端中node的映射關係

2)如果是ASKING命令則命令會被執行,key不在遷移的節點已經被遷移到目標的節點

3)Key不存在則新建

 

4.1讀寫請求

 

槽裏面的key還未遷移,並且槽屬於遷移中

假如k1屬於槽x,並且k1還在Node A

4.2 MOVED請求

 

槽裏面的key已經遷移過去,並且槽屬於遷移完

假如k1屬於槽x,並且k1不在Node A,而且槽x已經遷移到Node B

 

4.3 ASK請求

 

槽裏面的key已經遷移完,並且槽屬於遷移中

假如k1屬於槽x,並且k1不在Node A,而且槽x還是MIGRATING狀態

 

 

5、通信故障


5.1故障檢測

集羣中的每個節點都會定期地向集羣中的其他節點發送PING消息,以此交換各個節點狀態信息,檢測各個節點狀態:在線狀態、疑似下線狀態PFAIL、已下線狀態FAIL。

當主節點A通過消息得知主節點B認爲主節點D進入了疑似下線(PFAIL)狀態時,

主節點A會在自己的clusterState.nodes字典中找到主節點D所對應的clusterNode結構,

並將主節點B的下線報告(failure report)添加到clusterNode結構的fail_reports鏈表中

struct clusterNode {

//...

//記錄所有其他節點對該節點的下線報告

list*fail_reports;

//...

};

每個下線報告由一個clusterNodeFailReport結構:

struct clusterNodeFailReport{

//報告目標節點已經下線的節點

structclusterNode *node;

//最後一次從node節點收到下線報告的時間

mstime_ttime;

}typedef clusterNodeFailReport;

如果集羣裏面,半數以上的主節點都將主節點D報告爲疑似下線,那麼主節點D將被標記爲已下線(FAIL)狀態,將主節點D標記爲已下線的節點會向集羣廣播主節點D的FAIL消息,

所有收到FAIL消息的節點都會立即更新nodes裏面主節點D狀態標記爲已下線。

將node標記爲FAIL需要滿足以下兩個條件:

1.有半數以上的主節點將node標記爲PFAIL狀態。

2.當前節點也將node標記爲PFAIL狀態。

 

5.2多個從節點選主

 

選新主的過程基於Raft協議選舉方式來實現的

1)當從節點發現自己的主節點進行已下線狀態時,從節點會廣播一條

CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息,並且具有投票權的主節點向這個從節點投票

2)如果一個主節點具有投票權,並且這個主節點尚未投票給其他從節點,那麼主節點將向要求投票的從節點返回一條,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支持從節點成爲新的主節點

3)每個參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,並根據自己收到了多少條這種消息來統計自己獲得了多少主節點的支持

4)如果集羣裏有N個具有投票權的主節點,那麼當一個從節點收集到大於等於集羣N/2+1張支持票時,這個從節點就成爲新的主節點

5)如果在一個配置紀元沒有從能夠收集到足夠的支持票數,那麼集羣進入一個新的配置紀元,並再次進行選主,直到選出新的主節點爲止

 

5.3故障轉移

 

當從節點發現自己的主節點變爲已下線(FAIL)狀態時,便嘗試進Failover,以期成爲新的主。

以下是故障轉移的執行步驟:

1)從下線主節點的所有從節點中選中一個從節點

2)被選中的從節點執行SLAVEOF NO NOE命令,成爲新的主節點

3)新的主節點會撤銷所有對已下線主節點的槽指派,並將這些槽全部指派給自己

4)新的主節點對集羣進行廣播PONG消息,告知其他節點已經成爲新的主節點

5)新的主節點開始接收和處理槽相關的請求



 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章