集群通過分片來進行數(shù)據(jù)共享谦疾,并提供復制和故障轉(zhuǎn)移
節(jié)點
- 1.一個集群有多個節(jié)點
- 2.節(jié)點代表的是master或者slave
-
3.當節(jié)點A發(fā)生CLUSTER MEET <IP> <PORT>給節(jié)點B(ip和port都是B節(jié)點的地址),兩個節(jié)點就會進行握手當握手成功后恐锦。節(jié)點B就會被加入到節(jié)點A所在的集群。
啟動節(jié)點
-
1.redis服務器在啟動的時候會根據(jù)cluster-enabled配置選項是否為yes來決定是否開啟服務器的集群模式
集群數(shù)據(jù)結(jié)構(gòu)
- 1.clusterNode結(jié)構(gòu)保存了一個節(jié)點的當前狀態(tài)货岭,比如節(jié)點的創(chuàng)建時間逝她,節(jié)點名字局劲,節(jié)點當前的配置紀元,節(jié)點的ip地址和端口號
-
2.每個節(jié)點都會使用一個clusterNode來記錄自己的狀態(tài)屈藐,并為集群中的所有其他節(jié)點(包括主從節(jié)點)
創(chuàng)建一個相應的clusterNode結(jié)構(gòu)(這就意味著每個節(jié)點都保存了集群中所有的clusterNode榔组,這些節(jié)點都被存放在clusterstate中的nodes屬性中)
-
3.其中l(wèi)ink屬性是一個clusterLink結(jié)構(gòu),該結(jié)構(gòu)保存了連接點所需要的有關(guān)信息联逻,比如套字接描述符搓扯,輸入緩沖區(qū)和輸出緩沖區(qū)(應該保存的是A節(jié)點到B節(jié)點之間的鏈接)
- 4.redisclient和cluster都有自己的套字接描述符合輸入,輸出緩沖區(qū)包归。前者是用于連接客戶端的锨推,后者是用于連接節(jié)點的。
-
5.clusterState記錄了在當前節(jié)點的視角下公壤,集群所處于的狀態(tài)换可,例如集群是在線還是下線,集群包含多少個節(jié)點厦幅,集群當前的配置紀元
cluster meet 命令的實現(xiàn)
- 1.我們像A節(jié)點發(fā)生cluster meet命令沾鳄,A節(jié)點根據(jù)ip和port去尋找B節(jié)點并進行握手。握手的過程如下:
- 2.A節(jié)點為B節(jié)點創(chuàng)建一個clusterNode結(jié)構(gòu)确憨,并放入A節(jié)點中的clusterState的nodes屬性(字典)
- 3.之后A節(jié)點像B節(jié)點發(fā)生一個MEET消息(前面只是握手成功洞渔,這邊是發(fā)送消息),如果一切順利缚态,則B節(jié)點在接收到A節(jié)點發(fā)生的MEET消息后磁椒,節(jié)點B會為節(jié)點A創(chuàng)建一個clusterNode,并加入到B自己的clusterState的nodes屬性(字典)
- 4.之后B向A返回一個PONG消息
- 5.通過PONG消息,A知道了B已經(jīng)接收到自己的MEET消息玫芦,之后A將向節(jié)點B返回一條PING消息
-
6.B收到PING消息后就是知道A已經(jīng)成功收到自己返回的PONG消息浆熔,握手完成
- 7.之后節(jié)點A會將節(jié)點B的信息通過GOSSIP協(xié)議傳播給集群中的其他節(jié)點,并讓其他節(jié)點與B進行握手桥帆。
槽指派
- 1.集群的整個數(shù)據(jù)庫被分為16384個槽(slot)医增,這意味著假如你有10個節(jié)點,每個節(jié)點有10個數(shù)據(jù)庫老虫,那么就相當于每個數(shù)據(jù)庫只能分到20分之一的slot
- 2.集群中的每個節(jié)點可以處理0個或者最多16384個slot
- 3.如果16384個槽點都有節(jié)點在處理叶骨,則集群處于上線狀態(tài)(ok),否則只要有一個槽沒有被集群處理祈匙,那么集群處于下線狀態(tài)
- 4.通過向節(jié)點發(fā)生cluster addslots命令忽刽,我們可以將一個或多個槽指派給節(jié)點負責
記錄節(jié)點的槽指派信息
-
1.clusterNode結(jié)構(gòu)的slots屬性和numslot屬性記錄節(jié)點負責處理哪些槽
- 2.slots屬性一個二進制位數(shù)組天揖,這個數(shù)組的長度為16384/8=2048字節(jié)(因為是位數(shù)組所以需要除8得到字節(jié),1字節(jié)=8位)跪帝,也就是說是16384位
- 3.redis以0為起始索引今膊,16383為終止索引,對slots數(shù)組中的16384個二進制位進行編號伞剑,并根據(jù)索引位上的值來判斷節(jié)點是否負責處理槽i
- 4.如果slots數(shù)組在索引i上的二進制位的值為1斑唬,那么標識該節(jié)點負責處理槽i,如果為0代表該節(jié)點不負責處理槽i
- 5.取出和設置slots數(shù)組中的任意一個二進制位的值的復雜度都是o(1)
- 6.numslots 是記錄該節(jié)點負責處理的slot的數(shù)量
傳播節(jié)點的槽指派信息
- 1.一個節(jié)點除了會將自己處理的槽記錄在clusterNode結(jié)構(gòu)的slots屬性和numslots屬性之外黎泣,它還會在將自己的slots數(shù)組通過消息發(fā)送給集群中的其他節(jié)點恕刘,以此來告知其他節(jié)點自己目前負責處理哪些槽。
- 2.當節(jié)點收到其他節(jié)點發(fā)送來的slots屬性后抒倚,其會在custerState.nodes字典中查找對應節(jié)點的ClusterNode結(jié)構(gòu)雪营,并對結(jié)構(gòu)中的slots數(shù)組進行更新
記錄集群中所有槽的指派信息
-
1.clusterState中的slots是clusterNode類型的
- 如果slots[i[指針指向Null代表槽未指向任何節(jié)點
- 如果指向一個clusterNode,標識該槽已經(jīng)指派給了clusterNode結(jié)構(gòu)所代表的節(jié)點
-
之所以還需要clusterState的slots是因為我們可以不需要遍歷就知道哪個槽分配給了哪個節(jié)點衡便,否則我們還需要遍歷clusterState中的nodes字典中的所有clusterNode結(jié)構(gòu)才能知道
CLUSTER ADDSLOTS命令的實現(xiàn)
- 該命令接受一個或多個槽作為參數(shù)献起,并將這些槽指派給接受該命令的節(jié)點負責
-
具體步驟如下圖
-
下面圖先展示了 一個未指派的槽和以及如何指派槽1,2
在集群中執(zhí)行命令
- 1.如果該槽正好就在該節(jié)點則直接指向,否則會返回MOVED錯誤
-
2.客戶端根據(jù),MOVED錯誤會指引客戶轉(zhuǎn)向redirect至正確的節(jié)點镣陕,并再次執(zhí)行想要執(zhí)行的命令
計算鍵屬于哪個槽(一共16384)
- 計算的命令是CRC(16) &16383谴餐,用于計算CRC-16校驗和,而&16383語句則用于計算出一個介于0到16383直接的整數(shù)作為鍵KEY的槽號
-
使用CLUSTER KEYSLOT <KEY> 可以查看一個給的的鍵屬于哪個槽
判斷槽是否由當前節(jié)點負責處理
- 當節(jié)點計算出鍵所屬的槽i之后呆抑,節(jié)點就會檢查自己在clusterState.slots數(shù)組中的項i岂嗓,判斷所在的槽是否由自己負責。
- 如果槽i是當前節(jié)點負責鹊碍,那么直接執(zhí)行并返回給客戶端
-
如果不屬于該節(jié)點厌殉,那么就會根據(jù)clusterstate.nodes[i]指向的節(jié)點的ip和port,然后像客戶端MOVED錯誤,指引客戶端轉(zhuǎn)向至正在處理槽i節(jié)點
MOVED錯誤
- MOVED <slot> <ip>:<port> 其中ip和port是正確的節(jié)點地址。
- MOVED命令是由節(jié)點轉(zhuǎn)向客戶端
- 一個集群的通常會與多個節(jié)點創(chuàng)建socket刑赶,所謂的節(jié)點轉(zhuǎn)向?qū)嶋H上就是換一個socket發(fā)送命令
- 如果客戶端尚未與想要鏈接的節(jié)點創(chuàng)建socket盛垦,那么客戶端會現(xiàn)根據(jù)MOVED的指令 提供的IP和port來鏈接節(jié)點矫夯,然后再進行轉(zhuǎn)向。
節(jié)點數(shù)據(jù)庫的實現(xiàn)
- 節(jié)點中 只能是用0號數(shù)據(jù)庫,而單機redis則沒有這個限制
-
還使用clusterstate結(jié)構(gòu)中的slots_to_keys跳躍表保存槽和鍵之間的關(guān)系
-
每當節(jié)點往數(shù)據(jù)庫新天一個鍵值時,節(jié)點就會對這個鍵以及鍵的槽號做關(guān)聯(lián)罐柳,比如lst鍵 所在的跳躍表分值為3347 嗲表所關(guān)聯(lián)的slot為3347
- 該跳躍表可以很表的對屬于某個或者某些槽的數(shù)據(jù)庫鍵進行批量操作
重新分片
- 指將任意數(shù)量的某個節(jié)點的slots分配給其他節(jié)點
- 重新分片可以在線進行,并且源節(jié)點和目標節(jié)點都可以繼續(xù)處理命令請求
-
redis-trib是負責執(zhí)行分片的具體步驟如下:
ASK錯誤
-
即當要處理的鍵已經(jīng)被遷移到目標節(jié)點狰住,那么源節(jié)點返回ASK錯誤张吉,指引客戶端找到真正的鍵所在的位置
- 一個槽可能包含多個鍵
cluster setslot importing命令的實現(xiàn)
-
clusterState結(jié)構(gòu)的importing_slots_from數(shù)組記錄了當前節(jié)點正在從其其他節(jié)點導入的槽
- 如果importing_slots_from[i]的值不為null,而是指向一個clusterNode結(jié)構(gòu)催植,那么標識當前節(jié)點正在從clusterNode所代表的節(jié)點導入槽i
-
cluster setslot<i> importing <source_id> 可以將目標節(jié)點的importing_slots_from[i]的值設置為source_id所代表節(jié)點的ClusterNode肮蛹,具體如下圖
cluster setslot migranting 命令的實現(xiàn)
-
clusterState結(jié)構(gòu)的migrating_slots_to數(shù)組記錄了當前節(jié)點正在遷移至其他節(jié)點的槽
- 如果migrating_slots_to[i]不為null 而是指向一個clusterNode結(jié)構(gòu)勺择,那么標識當前節(jié)點正在將槽i遷移至clusternode所代表的節(jié)點
-
命令cluster setSLOT<i> migrating <target_id>,代表將migrating_slots_to[i]設置為其即將遷移的節(jié)點的ClusterNode
ASK錯誤
比如下面的例子
ASKING命令
- 該命令唯一要做的就是打開發(fā)送該命令的客戶端的flags蔗崎,即REDIS_ASKING標識
-
節(jié)點判斷是否指向客戶端命令的過程如下
- 當節(jié)點按照最終的邏輯執(zhí)行槽i命令,但是槽i已經(jīng)遷移了 則返回ASK錯誤扰藕。
- 當客戶端轉(zhuǎn)向真正的節(jié)點的時候需要先發(fā)送ASKING命令 以此來區(qū)分自己是因為ASK才來這個節(jié)點執(zhí)行命令缓苛,否則會因為計算槽的算法返回MOVED錯誤
- REDIS_ASKING是一次性標識,當節(jié)點執(zhí)行了有REDIS_ASKING標識的客戶端命令后邓深,客戶端的該標識就會被清除
ASK錯誤和MOVED錯誤的區(qū)別
-
主要就是前者代表槽正在轉(zhuǎn)移未桥,后者代表槽不屬于自己管轄
復制與故障轉(zhuǎn)移
-
redis集群中主節(jié)點用于處理槽,而從節(jié)點則用于復制某個主節(jié)點
-
如果7000節(jié)點進入下線芥备,那么集群中仍在正在運作的一個主節(jié)點將在原7000節(jié)點的2個從節(jié)點選擇一個節(jié)點作為新的主節(jié)點
-
當7000重新上線后 只能成為7004的從節(jié)點
設置從節(jié)點
- cluster replicate <node_id> 可以讓接收到命令的節(jié)點成為所指定節(jié)點的從節(jié)點冬耿,并開始進行復制
- 接受到該命令的節(jié)點會在自己的clustestate.nodes中找到該node_id的clusterNode結(jié)構(gòu),并將自己的clusterState.myself.slaveof指針指向這個結(jié)構(gòu)萌壳,以此來記錄這個節(jié)點正在復制的主節(jié)點:
- 節(jié)點也會修改在自己的clusterState.myself.flags中的屬性亦镶,關(guān)閉原本的REDIS_NODE_MASTER標識,并打開REDIS_NODE_SLAVE標識
-
最后節(jié)點會調(diào)用復制方法
-
集群中的所有節(jié)點都會在代表主節(jié)點的clusterNode結(jié)構(gòu)的slaves屬性和numslaves屬性中記錄正在復制這個主節(jié)點的從節(jié)點名單
故障檢測
- 集群中的每個節(jié)點都會定期的像集群中的其他節(jié)點發(fā)送ping消息袱瓮,如果接受節(jié)點在規(guī)定時間回復PONG則ok缤骨,否則發(fā)送方會標記為接受方為疑似下線
- 集群中的各個節(jié)點會通過互相發(fā)送消息的方式來交換集群中各個節(jié)點的狀態(tài)信息。
-
當主節(jié)點A通過消息得知主節(jié)點B認為主節(jié)點C進入疑似下線尺借,那么主節(jié)點A會在自己的clusterState.nodes中找到主節(jié)點C所對應的ClusterNode結(jié)構(gòu)绊起,并將主節(jié)點B的下線報告添加到clusterNode結(jié)構(gòu)的fail_reports
-
如果一個集群半數(shù)疑似負責處理槽的主節(jié)點都將某個主節(jié)點X報告為疑似下線,那么主節(jié)點x將被標記下線燎斩,然后將消息廣播到其他節(jié)點
故障轉(zhuǎn)移
選舉新的主節(jié)點
- 與選舉sentinel選舉相似 都是基于raft算法選舉的
消息
節(jié)點發(fā)送的消息有五種
MEET(GOSSIP)消息:請求接受者加入到發(fā)送者所在的集群
PING(GOSSIP)消息:檢測是否在線
PONG(GOSSIP)消息:對MEET和PING消息的回復虱歪,也可以廣播向其他節(jié)點,刷新其他節(jié)點對自己的認識(比如故障轉(zhuǎn)移操作執(zhí)行成功之后)
FAIL消息:當節(jié)點A判斷節(jié)點B fail之后栅表,A會像集群廣播一條關(guān)于節(jié)點B的Fail消息笋鄙,其他收到消息這會立即將節(jié)點B標記為已下線
-
PUBLISH消息:節(jié)點接收到一條個該命令后節(jié)點會執(zhí)行該命令,并向集群中廣播一條PUBLISH消息
一條消息由消息頭(header)和正文(data)組成
消息頭
消息頭包含消息正文和記錄了消息發(fā)送者的一些信息
對應著clusterMsg結(jié)構(gòu)
-
其中clusterMsg.data 屬性指向了聯(lián)合ClusterMsgData 也就是消息的正文
- 節(jié)點可以根據(jù)clusterMsg結(jié)構(gòu)的currentEpoch怪瓶,sender局装,myslots等屬性找到發(fā)生者clusterNode結(jié)構(gòu)并進行更新
MEET ,PING,PONG消息的實現(xiàn)
-
redis集群的各個節(jié)點通過gossip協(xié)議來交換各自關(guān)于不同節(jié)點的狀態(tài)信息,其中MEET ,PING,PONG是gossip協(xié)議的三種消息實現(xiàn)劳殖,他們的正文都是clusterMsgDataGossip
因為三種消息正文相同铐尚,所以我們通過消息頭的type屬性來判斷一條消息具體是什么消息
-
每次發(fā)送這三種類型的消息的時候,發(fā)送這都從自己的已知節(jié)點列表隨機選擇兩個節(jié)點(主從都可以)哆姻,并將這兩個被選擇節(jié)點的消息分別保存到兩個clusterMsgDataGossip結(jié)構(gòu)中
-
接受者根據(jù)接受到信息去自己的已知列表中查看是否有被隨機選擇的節(jié)點信息宣增,有就更新,沒有就進行握手
FAIL消息的實現(xiàn)
- 主節(jié)點A將主節(jié)點B標記為已下線的FAIL時矛缨,節(jié)點A會像集群廣播一條關(guān)于主節(jié)點B的Fail消息爹脾,所有收到該消息的節(jié)點都會將節(jié)點B標記為已經(jīng)下線
- 如果我們使用gossip協(xié)議帖旨,那么可能需要多次通信才能被集群所有節(jié)點知道B下線,而使用Fail只需要一次
-
對應的數(shù)據(jù)結(jié)構(gòu)是clusterMsgDataFail
- 只包含一個nodename灵妨,其記錄了已下線節(jié)點的名字解阅,因為集群中名字獨一無二
PUBLISH消息的實現(xiàn)
- 比如客戶端使用publish channel message發(fā)向集群中的某個節(jié)點
- 接收到publish消息的及誒按不僅會向channel頻道發(fā)送message,還會像集群廣播一條publish消息泌霍,進而導致其他節(jié)點也會向該channel發(fā)送message
-
數(shù)據(jù)結(jié)構(gòu)是clusterMsgDataPublish
- bulk_data 是一個字節(jié)數(shù)組货抄,這個字節(jié)數(shù)組保存了客戶端通過publish命令發(fā)給節(jié)點的channel參數(shù)和message參數(shù)
- 其中bulk_data中的0-channel_len-1保存的是channel參數(shù),channel_len至channel_len+message_len_1字節(jié)則是保存的message參數(shù)
-
這個channel類似于mq中的channel