Redis集群通過分片的方式來保存數(shù)據(jù)庫中的鍵值對(duì):集群的整個(gè)數(shù)據(jù)庫被分為16384個(gè)槽(slot),數(shù)據(jù)庫中每個(gè)鍵都屬于這16384個(gè)槽的其中一個(gè)瓤漏,集群中的每個(gè)節(jié)點(diǎn)可以處理0個(gè)或最多16384個(gè)槽凿将。
當(dāng)數(shù)據(jù)庫總的16384個(gè)槽都有節(jié)點(diǎn)在處理時(shí)校套,集群處于上線狀態(tài)(ok);相反地牧抵,如果數(shù)據(jù)庫中有任何一個(gè)槽沒有得到處理前普,那么集群處于下線狀態(tài)(fail)拴测。
通過向節(jié)點(diǎn)發(fā)送CLUSTER ADDSLOTS命令,我們可以將一個(gè)或多個(gè)槽指派(assign)給節(jié)點(diǎn)負(fù)責(zé):
CLUSTER ADDSLOTS <slot> [slot ...]
本節(jié)接下來的內(nèi)容將首先介紹節(jié)點(diǎn)保存槽指派信息的方法,以及節(jié)點(diǎn)之間傳播槽指派信息的方法浦夷,之后再介紹CLUSTER ADDSLOTS命令的實(shí)現(xiàn)。
2.1 記錄節(jié)點(diǎn)的槽指派信息
clusterNode結(jié)構(gòu)的slots屬性和numslot屬性記錄了節(jié)點(diǎn)負(fù)責(zé)處理哪些槽:
struct clusterNode {
// ...
unsigned char slots[16384/8];
int numslots;
// ...
};
slots屬性是一個(gè)二進(jìn)制位數(shù)組(bit array)举娩,這個(gè)數(shù)組的長(zhǎng)度為16384/8=2048個(gè)字節(jié)馒稍,共包含16384個(gè)二進(jìn)制位。
Redis以0為起始索引映琳,16383為終止索引机隙,對(duì)slots數(shù)組中的16384個(gè)二進(jìn)制位進(jìn)行編號(hào),并根據(jù)索引i上的二進(jìn)制位的值來判斷節(jié)點(diǎn)是否負(fù)責(zé)處理槽i:
- 如果slots數(shù)組在索引i上的二進(jìn)制位的值為1萨西,那么表示節(jié)點(diǎn)負(fù)責(zé)處理槽i有鹿;
- 如果slots數(shù)組在索引i上的二進(jìn)制位的值為0,那么表示節(jié)點(diǎn)不負(fù)責(zé)處理槽i谎脯;
至于numslots屬性則記錄節(jié)點(diǎn)負(fù)責(zé)處理的槽的數(shù)量葱跋,也即是slots數(shù)組中值為1的二進(jìn)制位的數(shù)量。
2.2 傳播節(jié)點(diǎn)的槽指派信息
一個(gè)節(jié)點(diǎn)除了會(huì)將自己負(fù)責(zé)處理的槽記錄在clusterNode結(jié)構(gòu)的slots屬性和numslots屬性之外源梭,它還會(huì)將自己的slots數(shù)組通過消息發(fā)送給集群中的其他節(jié)點(diǎn)娱俺,以此來告知其他節(jié)點(diǎn)自己目前負(fù)責(zé)處理哪些槽。
當(dāng)節(jié)點(diǎn)A通過消息從節(jié)點(diǎn)B那里接收到節(jié)點(diǎn)B的slots數(shù)組時(shí)废麻,節(jié)點(diǎn)A會(huì)在自己的clusterState.nodes字典中查找節(jié)點(diǎn)B對(duì)應(yīng)的clusterNode結(jié)構(gòu)荠卷,并對(duì)結(jié)構(gòu)中的slots數(shù)組進(jìn)行保存或者更新。
因?yàn)榧褐械拿總€(gè)節(jié)點(diǎn)都會(huì)將自己的slots數(shù)組通過消息發(fā)送給集群中的其他節(jié)點(diǎn)烛愧,并且每個(gè)接收到slots數(shù)組的節(jié)點(diǎn)都會(huì)將數(shù)組保存到相應(yīng)節(jié)點(diǎn)的clusterNode結(jié)構(gòu)里面油宜,因此掂碱,集群中的每個(gè)節(jié)點(diǎn)都會(huì)知道數(shù)據(jù)庫中的16384個(gè)槽分別被指派給了集群中的哪些節(jié)點(diǎn)。
2.3 記錄集群所有槽的指派信息
clusterState結(jié)構(gòu)中的slots數(shù)組記錄了集群中所有16384個(gè)槽的指派信息:
typedef struct clusterState {
// ...
clusterNode *slots[16384];
// ...
} clusterState;
slots數(shù)組包含了16384個(gè)項(xiàng)慎冤,每個(gè)數(shù)組項(xiàng)都是一個(gè)指向clusterNode結(jié)構(gòu)的指針:
- 如果slots[i]指針指向NULL疼燥,那么表示槽i尚未指派給任何節(jié)點(diǎn)。
- 如果slots[i]指針指向一個(gè)clusterNode結(jié)構(gòu)蚁堤,那么表示槽i已經(jīng)指派給了clusterNode結(jié)構(gòu)所代表的節(jié)點(diǎn)醉者。
如果只將槽指派信息保存在各個(gè)節(jié)點(diǎn)的clusterNode.slots數(shù)組里,會(huì)出現(xiàn)一些無法高效地解決的問題违寿,而clusterState.slots數(shù)組的存在解決了這些問題:
- 如果節(jié)點(diǎn)只使用clusterNode.slots數(shù)組來記錄槽的指派信息湃交,那么為了知道槽i是否已經(jīng)被指派,或者槽i被指派給了哪個(gè)節(jié)點(diǎn)藤巢,程序需要遍歷clusterState.nodes字典中的所有clusterNode結(jié)構(gòu)搞莺,檢查這些結(jié)構(gòu)的slots數(shù)組,直到找到負(fù)責(zé)處理槽i的節(jié)點(diǎn)為止掂咒,這個(gè)過程的復(fù)雜度為O(N)才沧,其中N為clusterState.nodes字典保存的clusterNode結(jié)構(gòu)的數(shù)量。
- 而通過將所有槽的指派信息保存在clusterState.slots數(shù)組里面绍刮,程序要檢查槽i是否已經(jīng)被指派温圆,又或者取得負(fù)責(zé)處理槽i的節(jié)點(diǎn),只需要訪問clusterState.slots[i]的值即可孩革,這個(gè)操作的復(fù)雜度僅為O(1)岁歉。
要說明的一點(diǎn)是,雖然clusterState.slots數(shù)組中記錄了集群中所有槽的指派信息膝蜈,但使用clusterNode結(jié)構(gòu)的slots數(shù)組來記錄單個(gè)節(jié)點(diǎn)的槽指派信息仍然是有必要的:
- 因?yàn)楫?dāng)程序需要將某個(gè)節(jié)點(diǎn)的槽指派信息通過消息發(fā)送給其他節(jié)點(diǎn)時(shí)锅移,程序只需要將相應(yīng)節(jié)點(diǎn)的clusterNode.slots數(shù)組整個(gè)發(fā)送出去就可以了。
- 另一方面饱搏,如果Redis不使用clusterNode.slots數(shù)組非剃,而單獨(dú)使用clusterState.slots數(shù)組的話,那么每次要將節(jié)點(diǎn)A的槽指派信息傳播給其他節(jié)點(diǎn)時(shí)推沸,程序必須先遍歷整個(gè)clusterState.slots數(shù)組备绽,記錄節(jié)點(diǎn)A負(fù)責(zé)處理哪些槽,然后才能發(fā)送節(jié)點(diǎn)A的槽指派信息鬓催,這比直接發(fā)送clusterNode.slots數(shù)組要麻煩和低效得多肺素。
clusterState.slots數(shù)組記錄了集群中所有槽的指派信息,而clusterNode.slots數(shù)組只記錄了clusterNode結(jié)構(gòu)所代表的節(jié)點(diǎn)的槽指派信息深浮,這是兩個(gè)slots數(shù)組的關(guān)鍵區(qū)別所在压怠。
2.4 CLUSTER ADDSLOTS命令的實(shí)現(xiàn)
CLUSTER ADDSLOTS命令接受一個(gè)或多個(gè)槽作為參數(shù),并將所有輸入的槽指派給接收該命令的節(jié)點(diǎn)負(fù)責(zé):
CLUSTER ADDSLOTS <slot> [slot ...]
CLUSTER ADDSLOTS命令的實(shí)現(xiàn)可以用以下偽代碼來表示:
def CLUSTER_ADDSLOTS(*all_input_slots):
# 遍歷所有輸入槽飞苇,檢查它們是否都是未指派槽
for i in all_input_slots:
# 如果有哪怕一個(gè)槽已經(jīng)被指派給了某個(gè)節(jié)點(diǎn)
# 那么向客戶端返回錯(cuò)誤菌瘫,并終止命令執(zhí)行
if clusterState.slots[i] != NULL:
reply_error()
return
# 如果所有輸入槽都是未指派槽
# 那么再次遍歷所有輸入槽,將這些槽指派給當(dāng)前節(jié)點(diǎn)
for i in all_input_slots:
# 設(shè)置clusterState結(jié)構(gòu)的slots數(shù)組
# 將slots[i]的指針指向代表當(dāng)前節(jié)點(diǎn)的clusterNode結(jié)構(gòu)
clusterState.slots[i] = clusterState.myself
# 訪問代表當(dāng)前節(jié)點(diǎn)的clusterNode結(jié)構(gòu)的slots數(shù)組
# 將數(shù)組在索引i上的二進(jìn)制位設(shè)置為1
setSlotBit(clusterState.myself.slots, i)