Redis的cluster模式
Redis叢集是Redis提供的分散式資料庫方案,叢集通過分片(Sharding)來進行資料共享,並提供複製和故障轉移功能。
節點
一個節點就是一個執行在叢集模式下的Redis伺服器,Redis伺服器在啟動的時候會根據cluster-enabled
配置項來決定是否開啟伺服器的叢集模式。
- 節點當前狀態
typedef struct clusterNode { // 建立節點時間 mstime_t ctime; // 節點的名字,40個十六進位制字元組成 char name[CLUSTER_NAMELEN]; // 節點標識 // 使用各種不同的標識值記錄節點的角色(比如:主節點、從節點) // 以及節點目前所處的狀態(比如:線上或者下線) int flags; // 節點當前的配置紀元,用於實現故障轉移 uint64_t configEpoch; // 記錄節點的槽指派資訊 unsigned char slots[CLUSTER_SLOTS/8]; // 記錄節點負責處理的槽的數量 int numslots; // 主節點的slave節點數量 int numslaves; // 從節點資訊 struct clusterNode **slaves; // …… // 節點的ip地址 char ip[NET_IP_STR_LEN]; // 節點的埠號 int port; // 儲存連線節點所需的有關資訊 clusterLink *link; // …… } clusterNode;
- 節點所需的有關資訊,可以理解為是
clusterNode
的擴充套件資訊用clusterNode.link
來儲存
typedef struct clusterLink {
// 連線的建立時間
mstime_t ctime;
// 輸出緩衝區
sds sndbuf;
// 輸入緩衝區
sds rcvbuf;
// 與這個連線相關聯的節點
struct clusterNode *node;
} clusterLink;
- 叢集狀態資訊,記錄了在當前節點的視角下,叢集目前所處的狀態
typedef struct clusterState { // 指向當前節點的指標 clusterNode *myself; // 叢集當前的配置紀元,用於實現故障轉移 uint64_t currentEpoch; // 叢集當前的狀態:是線上還是下線 int state; // 叢集中至少處理著一個槽的節點的數量 int size; // 叢集節點名單(包括myself節點) // 字典的鍵為節點的名字,字典的值為節點對應的clusterNode結構 dict *nodes; // …… } clusterState;
通過傳送CLUSTER MEET命令,可以讓傳送命令的節點A和接收命令的節點B彼此都新增到clusterNode.nodes
中,然後節點A將節點B的資訊通過Gossip協議傳播給叢集中的其他節點,讓其他節點也與節點B進行握手,最終,經過一段時間後,節點B會被叢集中的所有節點認識。
槽指派
Redis叢集通過分片的方式來儲存資料庫中的鍵值對:叢集的整個資料庫被分為16384
個槽,叢集中的每個節點可以處理0
個或者最多16384
個槽。所有的槽都有節點處理時,叢集就屬於上線狀態,否則下線狀態。
在叢集中執行的命令
當資料庫中的16384
個槽都進行了指派後,叢集就會進入上線狀態,客戶端就可以向叢集中的節點發送資料命令了,具體步驟如下:
- 計算鍵屬於哪個槽
- 判斷槽是否由當前節點負責處理,如果
clusterNode.nodes[i]
等於clusterNode.myself
,那就說明i
是由當前節點負責,節點可以執行客戶端傳送的命令 - MOVED錯誤,當節點發現鍵所在的槽並非由自己負責處理的時候,節點就會向客戶端返回一個MOVED錯誤(
MOVED <slot> <ip>:<port>
),引導客戶端轉向至正在負責槽的節點
重新分片
Redis叢集的重新分片操作可以將任意數量已經指派給某個節點(源節點)的槽改為指派給另一個節點(目標節點),並且相關槽所屬的鍵值對也會從源節點被移動到目標節點。
重新分片的步驟
Redis叢集的重新分片操作是由Redis的叢集管理軟體redis-trib
負責執行的,具體步驟如下:
redis-trib
對目標節點發送CLUSTER SETSLOT <slot> IMPORTING <source_id>
命令,讓目標節點準備好從源節點匯入屬於槽slot的鍵值對redis-trib
對源節點發送CLUSTER SETSLOT <slot> MIGRATING <target_id>
命令,讓源節點準備好將屬於槽slot的鍵值對遷移至目標節點redis-trib
向源節點發送CLUSTER GETKEYSINSLOT <slot> <count>
命令,獲得最多count
個屬於槽slot的鍵值對的鍵名- 對於步驟3獲得的每個鍵名,
redis-trib
都向源節點發送一個MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>
命令,將被選中的鍵原子地從源節點遷移至目標節點 - 重複執行步驟3和步驟4,直到源節點儲存的所有屬於槽slot的鍵值對都被遷移至目標節點為止
redis-trib
向叢集中的任意一個節點發送CLUSTER SETSLOT <slot> NODE <target_id>
命令,將槽slot指派給目標節點,這一個指派資訊會通過訊息傳送至整個叢集,最終叢集中的所有節點都會直到slot已經指派給了目標節點
ASK錯誤
當被遷移槽的一部分鍵值對儲存在源節點裡面,而另一部分鍵值對儲存在目標節點裡時,如果客戶端向源節點發送一個與資料庫鍵有關的命令,且要處理的鍵正好在遷移的槽時:
- 源節點會先在自己的資料庫裡查詢指定的鍵,如果找到就返回
- 如果源節點中沒能找到,那麼這個鍵可能已經被遷移到目標節點,源節點將向客戶端返回一個ASK錯誤,指引客戶端轉向正在匯入槽的目標節點
- 當客戶端接收到ASK錯誤並轉向正在匯入槽的節點時,客戶端會先向節點發送一個ASKING命令,然後才重新發送想要執行的命令,這是因為客戶端如果不傳送ASKING命令,而直接傳送想要執行的命令的話,那麼客戶端傳送的命令將被節點拒絕執行,返回MOVED錯誤
ASK錯誤和MOVED錯誤的區別
ASK錯誤和MOVED錯誤都會導致客戶端轉向,它們的區別在於:
- MOVED錯誤代表槽的負責權已經從一個節點轉移到了另一個節點
- ASK錯誤只是兩個節點在遷移槽的過程中使用的一種臨時措施
複製和故障轉移
Redis叢集中的節點分為主節點(master)和從節點(slave),其中master用於處理槽,而slave用於複製某個master,並在被複制的master下線時,代替下線master繼續處理命令請求
設定從節點
向一個節點發送CLUSTER REPLICATE <node_id>
命令,可以讓接收命令的節點成為node_id
所指定節點的從節點,並開始對主節點進行復制
故障檢測
叢集中每個節點都會定期地向叢集中的其他節點發送PING訊息,以此來檢測對方是否線上。
- 如果接收PING訊息的節點沒有在規定的時間內,向傳送PING訊息的節點返回PONG訊息,那麼傳送PING訊息的節點就會將接收PING訊息的節點標記為疑似下線
- 如果一個叢集裡面,半數以上負責處理槽的主節點都將某個主節點A報告為疑似下線,那麼這個主節點A將被標記為已下線,並向叢集廣播一個關於主節點A已下線的訊息,所有收到訊息的主節點都會立即將主節點A標記為已下線
故障轉移
當一個節點發現自己正在複製的主節點已下線時,從節點將開始對下線主節點進行故障轉移,步驟如下:
- 複製下線主節點的所有從節點裡面,會有一個從節點被選中
- 被選中的從節點執行
SLAVEOF no one
命令,成為新的主節點 - 新的主節點會撤銷所有已下線主節點的槽指派,並將這些槽指派全部指向自己
- 新的主節點向叢集廣播一條PONE訊息,通知所有其他節點自己已經成功接管了已下線節點負責處理的槽
- 新的主節點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成