Redis学习之集群(五)
目錄
節點的數據結構
一個Redis集群有多個節點組成,每個節點的數據結構是一個clusterNode:
struct clusterNode{//創建節點時間mstime_t ctime;//節點名稱char name[REDIS_CLUSTER_NAME];//節點的狀態及集群角色int flat;//配置紀元,用于故障轉移unit64_t configEpoch;//連接節點所需信息clusterLink *link;//節點狀態clusterState *state }其中clusterLink保存連接節點所需要信息,如套接字描述符,輸入緩存區和輸出緩存區:
typedef struct clusterLink{//連接的創建時間mstime_t ctime;//TCP套接字描述符int fd;//輸出緩存區sds sndbuf;//輸入緩存區sds rcvbuf;//與這個連接相連接的節點struct clusterNode *node; }clusterLink;clusterNode的clusterState結構保存著在當前節點的視角下,集群目前所處的狀態。
typedef struct clusterState{//指向當前節點的指針clusterNode *myself;//配置紀元,用于故障轉移unit64_t configEpoch;//集群當前的狀態:是在線還是下線int state;//集群規模int size;//集群節點名單dict *nodes; }clusterState;其中myself指向自己的clusterNode結構,nodes保存著集群中所有節點的信息。
節點的數據結構可以用圖來表示出來:
2. 槽指派及重新分片
槽指派
Redis集群通過分片的方式來保存數據庫中的鍵值對,集群的整個數據庫被分成16384個槽,數據庫中的每個鍵都屬于著16384個槽的其中一個。
如果16384個槽都有節點處理,則集群處于上線狀態,否則處于下線狀態。
下面是常見的幾個命令:
# 將0-5000槽分派給7000端口的redis節點 127.0.0.1:7000 > CLUSTER ADDSLOTS 0 1 2 3 ... 5000 # 查看所有節點的信息及其負責的槽 127.0.0.1:7000 > CLUSTER NODES # 計算鍵屬于哪個槽 127.0.0.1:7000> CLUSTER KEYSLOT "haha" (integer) 2022每個節點的clusterNode結構記錄著那些節點處理哪寫槽:
struct clusterNode{unsigned char slots[16384/8];int numslots; }numslots記錄著cluterNode負責處理的槽數量,slots數組是一個二進制位數組,記錄著每個槽的信息,用1代表節點處理這個槽,用0代表節點不處理這個槽。除以8是因為一個char有8個bit。
從上面的節點的數據結構,我們知道每個節點用clusterState來記錄集群的狀態,clusterState結構中的slots數組會記錄集群中所有槽的指派信息。
typedef struct clusterState{clusterNode *slots[16384]; }為什么要用clusterState來記錄所有槽的信息而不是通過遍歷clusterState的nodes,遍歷其中每個節點的slots數組來獲取槽的信息呢?答案很簡單,通過clusterState.slots[i] 我們就能直接得到節點信息,復雜度為o(1)
因此節點與槽的關系可以用下圖來表示:
重新分片
Redis集群的重新分片操作可以將任意數量已經指派給某個節點的槽改為指派給另一個節點,并且相關槽所屬的鍵值對也會從源節點被移動到目標節點。
分片操作由Reidis集群管理軟件redis-trib負責執行,步驟如下:
3. MOVE錯誤及ASK錯誤
MOVE錯誤
當節點發現鍵所在的槽并非由自己負責處理的時候,節點就會向客戶端返回一個MOVED錯誤,指引客戶端轉向正在負責槽的節點。
例子:向節點7000得到一個槽,但是這個槽在7001中。
ASK錯誤
clusterNode有兩個特殊的數組,importing_slots_from記錄著當前節點從其他節點導入的槽,用migrating_slots_to數組記錄著當前節點正在遷移至其他節點的槽。
typedef struct clusterState{clusterNode *importing_slots_from;clusterNode * migrating_slots_to; }如果節點收到一個key的命令請求,這個節點先會嘗試在自己的數據庫中查找key,如果找到就直接執行客戶端發送的命令;如果沒有找到,那么會檢查clusterState.migrating_slots_to[i],查看key所屬的槽i時候正在遷移,如果正在遷移的話,那么節點會客戶端發送一個ASK錯誤,引導客戶端到正在導入槽i的節點去查找鍵key。
步驟:
所以MOVE錯誤代表槽的負責權已經由一個節點轉移到另一個節點,每次客戶端遇到槽i的命令請求都可以直接發送給MOVED錯誤所指向的節點。ASK錯誤只是在遷移槽過程中的一種臨時措施,不會對客戶端今后發送關于槽i的命令請求產生任何影響。
4. 復制與故障轉移
首先用一幅圖了解復制與故障轉移的概念:
設置從節點
CLUSTER REPLICATE <node_id>首先接收到命令的節點首先在自己的clusterState.nodes找到node_id所對應的節點的clusterNode結構,并將自己的clusterState.myself.slaveof指向這個結構。
節點修改自己在culsterState.myself.flags的屬性,關閉原來的REDIS_NODE_MASTER標識,設置為REDIS_NODE_SLAVE標識,表示這個節點由原來的主節點變成了從節點。
用clusterState.myself.slaveof指向的clusterNode結構中的IP地址和端口號,對主節點進行復制。
### 故障檢測
集群中的每個節點都會定期地向其他節點發送一條PING命令,如果在規定的時間內,接受PING消息消息的節點沒有向發送PING的節點發送PONG消息,那么發送PING消息的節點將會將接收PING消息的節點設置成疑似下線。
其中clusterNode用它的fail_report鏈表來更新節點的下線狀態:
struct clusterNode{//一個鏈表,來記錄所有其他節點對該節點的下線報告list *fail_reports; }每個下線報告由一個clusterNodeFailReport表示
struct clusterNodeFailReport{//報告下線的節點struct clusterNode *node;//最后一次從node節點收到下線報告的時間mstime_t time; }過程:當一個主節點A通過消息得知主節點B和C認為D進入了疑似下線狀態時,主節點A會在自己的clusterState.nodes字典中找到主節點D所對應的clusterNode結構,并將主節點B和C的下線報告添加到clusterNode結構的fail_reports鏈表中。
如果在一個集群里面,半數以上負責處理槽的主節點將某個主節點x報告為疑似下線(PFAIL),那么這個主節點x將會被標記為已下線(FAIL),然后這個將主節點x標記為已下線的的節點會向集群廣播一條關于主節點x的FAIL消息,然后所有的收到FAIL消息的節點會立即將主節點x標記為已下線。
### 選舉主節點
這個選舉方法與Sentinel的選舉都是基于Raft算法,可以參考Sentinel的選舉:
每個Sentinel都有成為領頭的能力,而且每次選舉無論是否成功,都會將配置紀元(confuguration epoch)的值自增,它實際上就是一個計數器。
局部領頭:當一個Sentinel A向另一個Sentinel B發送請求SENTINEL is-master-down-by-addr + (Sentinel A 的 runid )代表A想成為B的局部領頭。
所以這種規則就是先到先得,最早向目標Sentinel發送這個命令的必然成為它的Sentinel,后面的命令都會無效,當它的票數超過半數時,它就成為領頭Sentinel,然后對已經下線的主服務器執行故障轉移操作。
故障轉移
總結
以上是生活随笔為你收集整理的Redis学习之集群(五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis学习之Sentinel(四)
- 下一篇: 数据库-DQL练习(附答案)