1. 程式人生 > 其它 >《Redis設計與實現》讀書筆記(二十八) ——Redis叢集節點結構與槽分配

《Redis設計與實現》讀書筆記(二十八) ——Redis叢集節點結構與槽分配

《Redis設計與實現》讀書筆記(二十八) ——Redis叢集節點結構與槽分配

(原創內容,轉載請註明來源,謝謝)

一、概述

redis叢集是redis的分散式資料庫的解決方案,叢集通過分片(sharding)來進行資料共享,並提供複製和故障轉移的功能。

二、叢集的節點

1、節點組成

一個redis叢集由多個節點組成,每個節點是一個執行在叢集模式下的redis伺服器。叢集還沒建立好時,每個節點可以看成是一個獨立的叢集,將各個節點聯絡起來,就會形成一個真正有效的叢集。

叢集的命令是,clustermeet <ip> <port>,向一個節點node傳送此命令傳送給另一節點,可以讓節點node與傳送的ip:port進行一次握手,成功後node將記錄該ip與port,新增到叢集中。

clusternode命令,可以檢視當前節點的叢集。

2、啟動節點

啟動節點的時候,伺服器會根據redis配置檔案中的cluster-enabled選項,來決定該伺服器是否開啟叢集模式。

由於節點也是一個redis伺服器,因此其會使用redis伺服器的各種特性,包括用檔案事件來處理命令和回覆、執行serverCron定時函式、鍵值對儲存的方式、rdb與aof持久化、釋出訂閱、複製、lua指令碼環境與執行lua指令碼過程、用redisServer儲存伺服器狀態、redisClient儲存客戶端狀態等。

但是,叢集的節點也有一些特殊性,包括特有的定時函式clusterCron(用於節點的其他資料傳送gossip訊息,檢查節點是否斷線,檢查是否需要對下線節點進行故障轉移等),另外還有叢集節點特殊的資料結構,儲存在cluster.h檔案中,包括clusterNode、clusterLink、clusterState等。

三、叢集節點資料結構

1、clusterNode結構

該結構儲存節點的當前狀態,包括名字、ip地址、建立時間、配置紀元、埠號等。叢集裡的每個節點,都會建立一個clusterNode來記錄自身狀態,併為叢集中的其他節點(包括主從節點)建立相應的clusterNode結構。

主要資訊如下:

         struct clusterNode{
         mstime_t ctime;//節點建立的時間
         char name[REDIS_CLUSTER_NAMELEN];//節點名稱,由40位16進位制字元組成
         int flags;//節點標識,標記節點主從、是否下線等狀態
         uint64_tconfigEpoch;//節點建立紀元,用於故障轉移
         char ip[REDIS_IP_STR_LEN];//節點ip地址
         int port;//節點埠號
         clusterLink *link;//儲存節點所需的有關資訊
         //….其他內容省略
}

2、clusterLink結構

該結構是儲存在clusterNode中的一個屬性,其用於儲存節點所需有關資訊,如套接字描述符、輸入緩衝區和輸出區緩衝等。

         typedef struct clusterLink{
         mstime_t ctime;//節點連線的建立時間
         int fd;//套接字描述符
         sds sndbuf;//輸出緩衝區,儲存等待發送給其他節點的資訊
         sds rcvbuf;//輸入緩衝區,儲存從其他節點接收到的的資訊
         struct clusterNode*node;//與這個連線相關的節點,沒有就是null
}

redisClient和clusterLink結構都有套接字和相應的輸入和輸出緩衝區,但是區別在於redisClient是用於連線客戶端,clusterLink用於連線其他節點。

3、clusterState結構

每個節點都儲存一個這個結構,記錄當前節點視角下,叢集目前所處的狀態,例如叢集是否線上、當前有幾個節點、當前配置紀元等。

         typedef struct clusterState{
         clusterNode  *myself;//指向當前節點的指標
         uint64_tcurrentEpoch;//叢集當前配置紀元,用於故障轉移
         int state;//叢集當前狀態,是否線上
         int size;//叢集中至少處理一個槽的節點數量
         dict *nodes;//叢集節點名單,包括myself節點,鍵是節點名稱,值是對應的cluster Node結構
         //….其他資訊
}clusterState;

4、節點結構

三個節點組成的叢集,如下圖所示:

其中,每個節點都會有一個這樣的結構,區別在於myself指標都是指向各自節點自己。

4、cluster meet實現

cluster meet <ip> <port>,對節點a發節點b的ip,則將節點b加入到節點a的叢集中,同時節點b也會將jieda加入到叢集中。流程如下:

1)節點a為節點b建立一個clusterNode結構,並將該結構新增到自己的clusterState.nodes字典裡面。

2)節點a根據ip和埠號,給節點b傳送一條meet訊息。

3)b會為a建立一個clusterNode,也新增到b自己的clusterState.nodes。

4)b向a回覆pong資訊。

5)a收到後,會再給b傳送一個ping資訊。

6)b接收到a的ping資訊後,握手完成。

握手流程如下:

7)之後,節點a會將節點b的資訊,以gossip協議傳播給a當前叢集中的其他節點,這樣其他節點也會與b進行上述操作。

四、槽(slot)指派

1、概述

redis叢集通過分片的方式,來儲存鍵值對。叢集的整個資料庫被分為16384個槽,資料庫中的每個鍵,都屬於16384個槽中的一個,叢集每個節點可以處理0~16384個槽。

當叢集中,每一個槽都有節點在處理時,則這個叢集是上線(ok)的狀態;任意一個槽沒有節點處理,則該叢集下線(fail)。

採用命令cluster info,可以檢視當前叢集的狀態,ok是上線,fail是下線。

通過向節點發送clusteraddslots <slot1> [slot2 slot3 ….]命令,可以將槽指派給節點。槽是用數字從0~16383進行編號的。

在哪個節點輸入clusteraddslots,則對該節點指派槽。

2、記錄節點指派資訊

節點指派的槽的資訊,記錄在clusterNode結構體中:

         struct clusterNode{
         //….其他資訊
         unsigned char slots[16384/8];//二進位制陣列
         int numslots;//節點處理的槽數量
         };

slots是一個二進位制位陣列,長度是2048個位元組,共包含16384個二進位制位。每一個下標代表8個槽,用二進位制位表示。如果節點負責某個槽,則陣列下標對應的二進位制位的相應位置的值是1,否則是0。

例如,下圖中的陣列,表示節點負責的槽是1、3、5、8、9、10這幾個。

使用二進位制的方式,目的是便於獲取、修改節點負責的槽,因為時間複雜度都是O(1)。

3、傳播節點槽指派資訊

節點被分配了槽,不僅會記錄在節點自身的clusterNode結構體,還會將資訊傳播給叢集的其他節點。

節點a收到節點b的槽分配資訊,會從自身記錄節點b資訊clusterNode的結構中,相應的屬性slots與numslots記錄槽的位置與槽的數量。

4、記錄叢集所有槽指派資訊

指派資訊記錄在clusterState結構體中:

typedef stuct clusterState{
//….其他資訊
clusterNode *slots[16384];
}clusterState;

這個結構體中,陣列每個下標表示一個槽,下標的值都是指標,指向負責該槽的節點。如果某個陣列下標是null,表示目前沒有節點負責該槽。

redis的設計非常巧妙,該slots屬性可以快速找到每個槽對於的負責的節點,而節點內部clusterNode結構的slots,可以快速查詢、改變某個節點負責的槽,且獲取某個節點負責的全部槽的速度比從clusterState的slots中快得多。

5、槽指派的實現

槽指派之前,會先檢查槽是否已經有節點負責,如果一個或以上的槽已經有節點負責,則停止指派,並且報錯。

如果所有槽都沒有節點負責,則修改clusterState的slots陣列,將每個槽下標的值指向該節點的clusterNode結構;並修改該clusterNode的slots陣列,將槽對應的二進位制位置設定成1。

例如,執行命令clusteraddslots 1 2,節點變化如下:

完成上述命令寫入後,節點會發訊息通知叢集的其他節點。

—written by linhxx 2017.09.15