1. 程式人生 > 實用技巧 >Redis的cluster模式

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個槽都進行了指派後,叢集就會進入上線狀態,客戶端就可以向叢集中的節點發送資料命令了,具體步驟如下:

  1. 計算鍵屬於哪個槽
  2. 判斷槽是否由當前節點負責處理,如果clusterNode.nodes[i]等於clusterNode.myself,那就說明i是由當前節點負責,節點可以執行客戶端傳送的命令
  3. MOVED錯誤,當節點發現鍵所在的槽並非由自己負責處理的時候,節點就會向客戶端返回一個MOVED錯誤(MOVED <slot> <ip>:<port>),引導客戶端轉向至正在負責槽的節點

重新分片

Redis叢集的重新分片操作可以將任意數量已經指派給某個節點(源節點)的槽改為指派給另一個節點(目標節點),並且相關槽所屬的鍵值對也會從源節點被移動到目標節點。

重新分片的步驟

Redis叢集的重新分片操作是由Redis的叢集管理軟體redis-trib負責執行的,具體步驟如下:

  1. redis-trib對目標節點發送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,讓目標節點準備好從源節點匯入屬於槽slot的鍵值對
  2. redis-trib對源節點發送CLUSTER SETSLOT <slot> MIGRATING <target_id>命令,讓源節點準備好將屬於槽slot的鍵值對遷移至目標節點
  3. redis-trib向源節點發送CLUSTER GETKEYSINSLOT <slot> <count>命令,獲得最多count個屬於槽slot的鍵值對的鍵名
  4. 對於步驟3獲得的每個鍵名,redis-trib都向源節點發送一個MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>命令,將被選中的鍵原子地從源節點遷移至目標節點
  5. 重複執行步驟3和步驟4,直到源節點儲存的所有屬於槽slot的鍵值對都被遷移至目標節點為止
  6. redis-trib向叢集中的任意一個節點發送CLUSTER SETSLOT <slot> NODE <target_id>命令,將槽slot指派給目標節點,這一個指派資訊會通過訊息傳送至整個叢集,最終叢集中的所有節點都會直到slot已經指派給了目標節點
ASK錯誤

當被遷移槽的一部分鍵值對儲存在源節點裡面,而另一部分鍵值對儲存在目標節點裡時,如果客戶端向源節點發送一個與資料庫鍵有關的命令,且要處理的鍵正好在遷移的槽時:

  1. 源節點會先在自己的資料庫裡查詢指定的鍵,如果找到就返回
  2. 如果源節點中沒能找到,那麼這個鍵可能已經被遷移到目標節點,源節點將向客戶端返回一個ASK錯誤,指引客戶端轉向正在匯入槽的目標節點
  3. 當客戶端接收到ASK錯誤並轉向正在匯入槽的節點時,客戶端會先向節點發送一個ASKING命令,然後才重新發送想要執行的命令,這是因為客戶端如果不傳送ASKING命令,而直接傳送想要執行的命令的話,那麼客戶端傳送的命令將被節點拒絕執行,返回MOVED錯誤
ASK錯誤和MOVED錯誤的區別

ASK錯誤和MOVED錯誤都會導致客戶端轉向,它們的區別在於:

  1. MOVED錯誤代表槽的負責權已經從一個節點轉移到了另一個節點
  2. 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標記為已下線
故障轉移

當一個節點發現自己正在複製的主節點已下線時,從節點將開始對下線主節點進行故障轉移,步驟如下:

  1. 複製下線主節點的所有從節點裡面,會有一個從節點被選中
  2. 被選中的從節點執行SLAVEOF no one命令,成為新的主節點
  3. 新的主節點會撤銷所有已下線主節點的槽指派,並將這些槽指派全部指向自己
  4. 新的主節點向叢集廣播一條PONE訊息,通知所有其他節點自己已經成功接管了已下線節點負責處理的槽
  5. 新的主節點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成