Redis集羣是Redis提供的分佈式數據庫方案,集羣經過分片來進行數據共享,並提供複製和故障轉移操做。node
一個Redis集羣一般由多個節點組成,在剛開始的時候每一個節點都是相互獨立的,他們處於一個只包含本身的集羣當中,咱們經過使用CLUSTER MEET命令將節點鏈接到一塊兒,構成一個包含多節點的集羣。redis
集羣的數據結構:算法
clusterNode結構保存了一個節點的當前狀態,好比節點建立時間、節點名稱、節點當前的配置紀元、節點的ip端口。每一個節點都會使用一個clusterNode結構記錄本身的狀態,併爲集羣中的全部其餘節點都建立一個相應的clusterNode結構。數據庫
struct clusterNode{ //建立節點的時間 mstime_t ctime; //節點的名稱,由40個十六進制字符組成 char name[REDIS_C:USTER_NAMELEN] //節點標識(標識節點的角色以及節點目前狀態) inf flags; //節點當前的配置紀元 uint64_t configEpochl; //節點的ip地址 char ip; //節點的端口號 int port; //保存鏈接節點所須要的有關信息 clusterLink *link; }
typedef struct clusterLink{ //鏈接的建立時間 mstime_t ctime; //TCP 套接字描述 int fd; //輸出緩衝區,保存着等待發送給其餘節點的消息 sds sndbuf; //輸入緩衝區,保存着從其餘節點接收到的消息 sds rcvbuf; //與這個鏈接相關聯的節點,若是沒有的話爲NULL struct clusterNode *node; } clusterLink;
每一個節點都保存着一個clusterState結構,這個結構記錄了在當前節點的視角下,集羣目前所處的狀態,數組
typedef struct clusterState{ //指向當前節點的指針 clusterNode *myself; //集羣當前的配置紀元,用於實現故障轉移。 uint64_t currentEpoch; //集羣當前的狀態:在線或下線 int state; //集羣中至少處理着一個槽的節點數量 int size; //集羣節點名單(包括myself節點) //字典的鍵爲節點的名稱,字典值爲節點對應的clusterNode結構 dict *node; } clusterState;
槽指派:服務器
Redis集羣經過分片的方式來保存數據庫中的鍵值對:集羣的整個數據庫被分紅16348個槽,數據庫中的每一個鍵都屬於16384個槽的其中一個,集羣中的每一個節點能夠處理0個最多16384個槽。數據結構
使用cluster meet 命令將節點鏈接到集羣裏面後,這時集羣仍處於下線狀態,由於集羣中的節點沒有處理任何槽分佈式
經過使用cluster addslots < slot > 命令,能夠爲節點分配槽學習
記錄節點的槽指派信息:ui
clusterNode 結構的slots屬性和numslot屬性記錄了節點負責處理那些槽:
struct clusterNode{ unsigned char slots[16348/8]; int numslots; };
同時,節點會將本身的slots數組經過消息發送給集羣中的其餘節點,以此來告知其餘節點本身目前負責處理那些槽。
clusterState結構中的slots數組記錄了集羣中全部16384個槽的指派信息。
typedef struct clusterState{ clusterNode *slots[16384]; }clusterState;
clusterState.slots是爲了更快的定位槽所在的節點O(i)。
clusterNode.slots 當程序須要將某個節點的槽指派信息經過消息發送給其餘節點時,程序只須要將相應節點的clusterNode.slots數組整個發送過去就能夠,clusterState.slots記錄了集羣中全部的槽指派訊息,而clusterNode.slots只記錄了當前節點的槽指派信息。
當客戶端向節點發送與數據庫鍵有關的命令時,接收命令的節點會計算出命令要處理的數據庫鍵屬於哪一個槽,並檢查這個槽是否指派給了本身:
若是鍵所在的槽正好是指派給了當前節點,那麼節點直接執行這個命令;若是鍵所在的槽並無指派給當前節點,那麼節點會向客戶端返回一個MOVED錯誤,指引客戶端轉向到正確節點,並再次發送以前想要執行的命令。
節點使用如下算法來計算給定鍵key屬於哪一個槽:
def slot_number(key): return CRC16(key) & 16383
當節點計算出鍵所屬的槽i以後,節點就會檢查本身在clusterState.slots數組中的項i,判斷所在的槽是否由本身負責:若是clusterState.slots[i]等於clusterState.myself,那麼說明槽i由當前節點負責,節點能夠執行客戶端發送的命令;反之節點會根據slusterState.slots[i]指向的clusterNode結構所記錄的節點IP和端口號,向客戶端返回MOVED錯誤指引客戶端轉向至再處理槽i的節點。
MOVED錯誤的格式爲:MOVED < slot > <ip>:<port>
當客戶端接收到節點返回的MOVED錯誤時,客戶端根據MOVED錯誤提供的IP地址和端口號,轉向至負責處理槽slot的節點,並向該節點從新發送以前想要執行的命令。一個集羣客戶端一般會與集羣中的多個節點建立套接字鏈接,而所謂的節點轉向實際上就是換一個套接字來發送命令。
集羣模式的redis-cli 客戶端在接收到MOVED錯誤時,並不會打印出MOVED錯誤,而是根據MOVED錯誤自動進行節點轉向,並打印出轉向信息,因此咱們時看不見節點返回的MOVED錯誤。
節點和單機服務器在數據庫方面的一個區別時,節點只能使用0號數據庫,而單機Redis服務器則沒有這一限制。除了將鍵值對保存在數據庫裏面以外,節點還會用clusterState結構中slots_to_keys跳躍表來保存槽和鍵之間的關係:
typedef struct clusterState{ zskiplist *slots_to_keys; } clusterState;
slots_to_keys跳躍表每一個節點的分值score都是一個槽號,而每一個節點的成員(member)都是一個數據庫鍵:每當節點往數據庫中添加一個新的鍵值對時,節點就會將這個鍵以及鍵的槽號關聯到slots_to_keys跳躍表;當節點刪除數據庫中的每一個鍵值對時,節點就會在slots_to_keys跳躍表解除被刪除鍵與槽號的關聯。
經過在slots_to_keys跳躍表中記錄各個數據庫鍵所屬的槽,節點能夠很方便地對屬於某個或某些槽的全部數據庫鍵進行批量操做。
Redis集羣的從新分片操做能夠將任意數量已經指派給某個節點(源節點)的槽改成指派給另外一個節點,而且相關槽所屬的鍵值對也會從源節點被移動到目標節點。從新分派操做能夠在線進行,在從新分片的過程當中,集羣不須要下線,而且源節點和目標節點均可以繼續處理命令請求。
Redis集羣的從新分片操做是由Redis的集羣管理軟件redis-trib負責執行的,Redis提供了進行從新分片所須要的全部命令,而redis-trib則經過源節點和目標節點發送命令來進行從新分片操做。
1)redis-trib對目標節點發送CLUSTER SETSLOT < slot > IMPORTING <source_id >命令,讓目標節點準備好從源節點導入屬於槽slot的鍵值對。
2)redis-trib對CLUSTER SETSLOT< slot > MIGATING < target_id > 命令,讓源節點準備好將屬於槽slot的鍵值對遷移至目標節點。
3)redis-trib向源節點發送CLUSTER GETKEYSINGSLOT < slot > < count > 得到最多count 個屬於槽slot的鍵值對的鍵名。
4)對於步驟3得到的鍵名,redis-trib都向源節點發送一個MIGRATE < target_ip > < target_port > < key_name > 0 <timeout> 命令,將被選中的鍵原子地從源節點遷移至目標節點。
5)重複 3,4步驟,直到全部鍵值對都被遷移至目標節點。
6)redis-trib向集羣中的任意一個節點發送CLUSTER SETSLOT < slot > NODE < target_id > 命令,將槽slot指派給目標節點,這一指派信息經過消息發送至整個集羣,最終集羣中的全部節點都會直到槽slot已經指派給了目標節點。
當客戶端向源節點發送一個與數據庫鍵有關的命令,而且命令要處理的數據庫鍵剛好就屬於正在被遷移的槽時:源節點會先在本身的數據庫裏查找指定的鍵,若是找到的話,就直接執行客戶端發送的命令;相反,若是源節點沒能在本身的數據庫裏找到指定的鍵,那麼這個鍵有可能已經被遷移到目標節點,源節點向客戶端返回一個ASK錯誤,指引客戶端轉向正在導入槽的目標節點,並再次發送以前想要執行的命令。
clusterState結構的importing_slots_from 數組記錄了當前節點正在從其餘節點導入的槽:
typedef struct clusterState{ clusterNode *importing_slots_from[16384]; } clusterState;
若是 importing_slots_from[i]的值不爲NULL,而是指向一個clusterNode結構,那麼標識當前節點正在從clusterNode所標識的節點導入槽i
clusterState結構migrating_slots_to數組記錄了當前節點正在遷移至其餘節點的槽:
typedef struct clusterState{ clusterNode *migratubg_slots_to[16384]; }clusterState;
若是migrating_slots_to[i]的值不爲NULL,而是指向一個clusterNode結構,那麼表示當前節點正在將槽i遷移至clusterNode所標識的節點。
ASK錯誤與MOVED錯誤的區別:
MOVED錯誤表明槽的負責權已經從一個節點轉移到另外一個節點:在客戶端收到槽i的MOVED錯誤以後,客戶端每次遇到關於槽i的命令請求時,均可以直接將命令請求發送至MOVED錯誤所指向的節點,由於該節點就是目前負責槽i的節點。
ASK錯誤只是兩個節點在遷移槽的過程當中使用的一種臨時措施。ASK錯誤的轉向不會對客戶端從此發送關於槽i的命令請求產生任何影響,客戶端仍然會將關於槽i的命令請求發送至目前負責處理槽i的節點。
天天學一點,總會有收穫。
說明:尊重做者知識產權,文中內容參考《Redis設計與實現》,僅在此作學習與你們分享。