轉自https://www.cnblogs.com/xiang9286/p/10948614.html
集群通過分片(sharding)來進行數據共享,並提供復制和故障轉移功能。
1.節點
一個節點就是一個運行在集群模式下的Redis服務器。啟動Redis服務器時,通過判斷cluster-enabled選項,選擇是否開啟集群模式。(Yes開啟集群,No則單機模式普通服務器)
一個Redis集群由多個節點組成,每個節點使用的端口各不相同,可以設置。每個節點最開始可以看做一個只有自己節點的集群,節點間通過命令相互握手,組建集群
握手命令
cluster meet 127.0.0.1 7001 //與ip為127.0.0.1,端口為 7001的節點握手 cluster nodes //顯示當前集群的節點信息
2.集群數據結構
clusterState 和
clusterNode 以及
slots

每個節點都有 clusterState結構。
clusterState結構的里面包含 :
slots[16384]數組,
myself屬性,(指向自己對應的clusterNode,直接通過該屬性訪問自己對應的clusterNode,這樣更快、方便各種自己節點信息的更新操作)
nodes屬性,
slots_to_key跳躍表,
importing_slots_from[16384]數組(記錄當前節點從其他節點導入的槽,為null未導入,指向clusterNode則為從該clusterNode對應的節點導入槽i)
migrateing_slots_to[16384]數組(記錄當前節點遷移到其他節點的槽,對應槽索引的值指向目標節點對應的clusterNode)
每個節點都有一個對應的clusterNode結構。
clusterNode中包含:
slots數組
numslot屬性
slaveof屬性---指向正在復制的主節點的對應clusterNode
flags屬性---REDIS_NODE_MASTER 表示該節點是主節點
REDIS_NODE_SLAVE 表示該節點時從節點
numslaves---復制該節點的從節點數量
slaves數組---每個元素都指向該節點下屬從節點的clusterNode
faile_reports鏈表---記錄其他節點對該節點的下線報告(在線、疑似下線PFAIL、已下線FAIL)
nodes字典記錄每個節點與clusterNode的對應關系
myself指向屬於該節點對應的clusterNode
例:設集群中目前有7000、7001、7002三個節點。對於7000端口的節點,其擁有一個clusterState結構,三個clusterNode結構(分別對應三個節點),myself屬性指向屬於自己的那個clusterNode節點。
3.槽指派
Redis集群通過分片的方式來保存數據庫中的鍵值對;集群的整個數據庫被分為16384個槽(索引為0~16383);數據庫中的每個鍵都屬於槽中的一個。每個節點都最多處理0~16384個槽。只有這16384個槽都被分配到節點時,Redis集群才處於上線狀態(ok);否則,只要有任意一個未分配,則集群處於下線狀態(fail)。
槽分配命令
127.0.0.1:7000> cluster addslots 0 1 2 3 ... 5000 //將0~5000的槽分配個7000節點
節點的 clusterState中的 slots[16384]數組,記載着集群中所有槽的指派信息,即槽是否被指派?被分配給了哪個節點?
節點的 clusterNode中的 slots數組則使用0-1標記法來表示,該節點處理的槽;處理,則對應槽索引值為1。使用 numslot屬性記錄該節點處理的槽總數
每個節點除了在自己對應的clusterNode中保存自己的槽分配信息外,還會將自己的slots數組通過消息發送給其他節點,讓其他節點保存。即對於7000節點而言,剩下的兩個clusterNode也會根據收到的其他節點槽相關信息,來更新clusterNode的屬性。(收到消息后,先通過clusterState.nodes查詢對應節點的clusterNode,再對其中的slots數組保存更新)
為什么同時使用clusterNode中的slots和clusterState的slots?
這是典型的以空間換時間。
1. 通過clusterState中的slots[]來查詢槽點是否被指派和指派節點的復雜度為 O(1);避免了通過nodes字典的映射去依次遍歷clusterNode中的slots[]
2.clusterNode中的slots[]可以用於作節點間互相發送槽信息。這樣就直接發送自己對應的clusterNode.slots[]即可,無需對clusterState的slots[]進行遍歷來找到自己負責了哪些槽了
總結:clusterState.slots[]數組記錄了集群中所有槽的指派信息;clusterNode.slots[]數組記錄了clusterNode結構所代表節點的槽指派信息。
4.節點數據庫
集群節點保存鍵值對以及鍵值對過期方式與單機數據庫一樣。
節點與單機服務器數據庫方面的一個區別就是:節點只能使用0號數據庫,即db[0](默認服務器啟動時,初始化16個數據庫)
clusterState的slots_to_key跳躍表保存 槽 和 鍵 之間的關系 (注意,每個節點的clusterState的跳躍表,只保存屬於自己節點處理的 槽 和 鍵 的對應關系)
分值(score)對應 槽號;成員(member)對應 數據庫鍵對象
用跳躍表保存,方便對某個或某些槽的所有數據鍵進行批量操作
5.重新分片
概念:將任意數量已經指派給某個節點的槽,改為指派給另一個節點,其中,槽對應的所有鍵值對都需要遷移。
特點:1、重新分片是可以在線進行的,集群無需下線
2、 重新分片過程中,源節點和目標節點都可以繼續處理命令請求(因為重新分片操作是鍵值對不斷的遷移?)
操作流程:重新分片操作由Redis的集群管理軟件 redis-trib 負責執行的
(對於單個槽的重新分片;多個槽就是單個槽的重復操作?)
1、
通知目標節點准備導入屬於槽slot的鍵值對。redis-trip向目標節點發送命令 (導入:import) 會修改目標節點的clusterState.importing_slots_from數組
> cluster setslot <slot> importing <source_id> //slot為槽的編號,source_id為源節點id
2、
通知源節點准備遷移屬於槽slot的鍵值對。redis-trip向源節點發送命令 (遷移:migrate) 會修改源節點的clusterState.migrating_slots_to數組
> cluster setslot <slot> migrating <target_id> //target_id為目標節點id
3、
從源節點處獲取屬於槽slot的鍵值對。redis-trip向源節點發送命令
> cluster getkeysinslot <slot> <count> //count為最多獲取count個鍵值對
4、
將獲得的鍵值對從源節點遷移到目標節點。對於獲得的每一個鍵值對,redis-trip都向源節點發送一個migrate命令
> migrate <target_id> <target_port> <key_name> 0 <timeout> //目標節點id、端口、鍵名、超時時間設置
5、重復3、4兩步操作,直到屬於槽slot的所有鍵值對都成功從源節點遷移至目標節點
6、
全部遷移成功后,將槽slot指派給目標節點。redis-trip向集群中任意一個節點發送命令,將slot指派給目標節點,
> cluster setslot <slot> NODE <target_id> //正式指派槽slot給界目標id的節點
然后這個信息會通過消息發送到整個集群,最終所有節點都會知道這個消息
(指派成功后,通過消息發送,通知整個集群中的節點,然后節點們根據消息,對節點內的clusterState結構和clusterNode結構進行更新?)
總結:先通知目標節點准備導入鍵值對,再通知源節點准備遷移鍵值對,然后開始鍵值對的遷移,鍵值對遷移成功后通過命令將槽指派給目標節點。鍵值對的遷移分為:從源節點獲取鍵值對(1次最多獲取count個),使用migrate命令將鍵值對從源節點遷移到目標節點,重復操作,直到鍵值對遷移完畢
需注意的是:如果經判斷發現槽中沒有保存鍵值對,則兩步准備后直接將槽指派給目標節點即可。
6.命令執行流程(是否處理該槽,是否存在鍵,是否正在遷移,是否是ASK命令轉向過來的等知識點)
流程圖:

1. 根據算法計算給定鍵key屬於哪個槽。值為i
CRC16(key) & 16383 //CRC校驗和 + &16383計算出一個位於0~16383之間的整數
2.
判斷該槽是否屬於本節點的處理范圍
通過clusterState.slots[i] == clusterState.myself 來判斷
3.1
該槽在處理范圍,則從當前節點的數據庫中查找鍵
3.1.1 查找到鍵,則執行命令
3.1.2 找不到鍵,則判斷該節點
是否正在遷移該槽(經由clusterState.migrating_slots_to[16384]數組判斷)
3.1.2.1 節點沒有遷移該槽,則向客戶端返回鍵查找不到錯誤。
3.1.2.2 節點正在遷移該槽,則向客戶端返回
ASK錯誤
> ASK i <ip>:<port> //i為鍵所在槽,ip和port分別為重新分片的目標節點的地址和端口
注意,ASK錯誤實際是不可見的
3.1.2.3 客戶端根據ASK錯誤,轉向找到遷移的目標節點
3.1.2.4 轉向后,先向目標節點發送
ASKING命令
3.1.2.5 發送ASKING命令后,再重新發送執行命令
3.2
該槽不在處理范圍,判斷客戶端是否帶有
ASKING標識(即是否先發送了
ASKING命令)
3.2.1 發送了ASKING標識,則破例執行關於該槽的命令一次
3.2.2 沒有事先發送ASKING命令,則向客戶端返回
MOVED錯誤,形式如下
> MOVED i<鍵所在的槽> <ip>:<port> //后面為負責處理該槽的節點的 ip和port
注意,MOVED命令實際是不可見的。只有單機數據庫下,客戶端無法讀懂該命令才會顯示
3.2.2.2 客戶端根據MOVED命令進行
轉向,轉到指定節點,並執行命令
7.ASK錯誤和MOVED錯誤比較
相同:都會導致客戶端轉向,都是不可見的。客戶端根據錯誤的信息自動轉向
不同:1.MOVED錯誤代表將槽的負責權從一個節點轉移到另一個節點,即永久轉向;下次客戶端遇到同樣槽的命令會直接訪問MOVED指向的節點
2.ASK錯誤只是兩個節點在遷移槽的過程中使用的一種臨時措施,下次客戶端遇到同樣槽的命令,仍然訪問源節點(即目前負責節點)
直到槽遷移完成,再次訪問就會收到MOVED命令
8.從節點,主節點,故障檢測,故障轉移(包括選取新主節點)
主節點負責處理槽,從節點用於復制某個主節點;當主節點下線時,在其所有從節點中選取一個用作主節點(類似備份?)(由Sentinel系統監控)
設置從節點
> cluster replicate <node_id> //接收命令的節點成為從節點,node_id為其主節點的id
設置從節點后,從節點對應的clusterNode的slaveof指針、flags屬性做出相應修改;主節點的slaves指針數組、numsslaves屬性也更新
集群中的節點之間通過互相發送消息的方式來交換集群中各個節點的狀態信息
故障檢測:節點間定期發送PING消息,以檢測對方是否在線;在線,則應在規定時間內返回PONG消息;
超時未返回,則標記為疑似已下線(PFAIL)並更新對方節點對應的clusterNode的fail_reports鏈表(下線報告鏈表)
狀態:在線、疑似已下線、已下線(FAIL)
當集群中一半以上主節點都將某個主節點X報告為疑似已下線時,則認為該主節點X已下線,並向集群廣播關於其下線的FAIL消息,讓其他節點將主節點X標記為已下線
故障轉移:
1、從復制該主節點的所有從節點里選出一個從節點
2、該從節點執行 Slaveof no one 命令,成為新主節點
3、新主節點撤銷所喲對已下線主節點的槽指派,將這些槽指派給自己
4、新主節點向集群廣播一條PONG消息,通知其他節點自己已成為新主界定啊,讓其他節點更新對應的clusterNode中的slots[]等屬性
5、新主節點開始接受和處理相關命令請求
如何選取新主節點:
選舉產生的。
基於Raft算法。(Sentinel系統的領頭Sentinel選舉也采用這種方式)
涉及 (1)集群的配置紀元,是一個自增計數器,初始值為0;每進行一次故障轉移操作,值+1
(2)每個主節點都有一次投票權,並且會把票投給第一個請求它投票的從節點(如果該主節點還未投票)
(3)從節點發現主節點下線后,會廣播一條消息,向所有主節點請求獲得投票支持
(4)主節點若向某個從節點投票,則投票同時會發送一條消息
(5)從節點對收到的消息進行統計
(6)設集群中有N個具有投票權的主節點,則當某個從節點收集到 N/2 + 1 張票時,該從節點成為新主節點
(7)若沒有滿足要求的從節點,則進入一個新的配置紀元,再次選舉。
9.集群中的消息發送
集群中的節點通過發送消息和接收消息來進行通信。發送消息的節點為發送者sender,接收消息的節點為接收者receiver
常見的五種消息
-
MEET消息:發送者接收到客戶端的 cluster meet 命令后,向指定節點發送MEET消息,邀請該節點進入發送者所在集群。
-
PING消息:用於檢測節點是否在線。發送該消息有兩種情況:(1)集群中每個節點默認每一秒胡會從已知節點中隨機選取5個節點,並從5個節點中選取最長時間未發送過PING消息的節點發送PING消息。(2)如果節點A最后一次收到節點B的PONG消息時間距當前時間,已超過節點A的cluster-node-timeout設置時長的一半,則A向B發送PING消息,以防止更新滯后
-
PONG消息:接收者收到MEET消息或PING消息后,向發送者返回PONG消息,以通知發送者這條消息已到達。
-
FAIL消息:節點下線消息。當節點A判斷節點B已下線時,則廣播關於B的FAIL消息,讓其他節點更改節點B的狀態為已下線。
-
PUBLISH消息:當節點收到PUBLISH命令時,執行命令同時廣播PUBLIS消息,所有節點都執行相同的PUBLISH命令。