Redis Cluster自己提供了自動將數據分散到Redis Cluster不一樣節點的能力,分區實現的關鍵點問題包括:如何將數據自動地打散到不一樣的節點,使得不一樣節點的存儲數據相對均勻;如何保證客戶端可以訪問到正確的節點和數據;如何保證從新分片的過程當中不影響正常服務。這篇文章經過了解這些問題來認識Redis Cluster分區實現原理。node
Redis Cluster是由多個同時服務於一個數據集合的Redis實例組成的總體,對於用戶來講,用戶只關注這個數據集合,而整個數據集合的某個數據子集存儲在哪一個節點對於用戶來講是透明的。Redis Cluster具備分佈式系統的特色,也具備分佈式系統如何實現高可用性與數據一致性的難點,由多個Redis實例組成的Redis Cluster結構一般以下:redis
Redis Cluster算法
Redis Cluster特色以下:數組
Redis Cluster中有一個16384長度的槽的概念,他們的編號爲0、一、二、3……1638二、16383。這個槽是一個虛擬的槽,並非真正存在的。正常工做的時候,Redis Cluster中的每一個Master節點都會負責一部分的槽,當有某個key被映射到某個Master負責的槽,那麼這個Master負責爲這個key提供服務,至於哪一個Master節點負責哪一個槽,這是能夠由用戶指定的,也能夠在初始化的時候自動生成(redis-trib.rb腳本)。這裏值得一提的是,在Redis Cluster中,只有Master才擁有槽的全部權,若是是某個Master的slave,這個slave只負責槽的使用,可是沒有全部權。Redis Cluster怎麼知道哪些槽是由哪些節點負責的呢?某個Master又怎麼知道某個槽本身是否是擁有呢?緩存
Master節點維護着一個16384/8字節的位序列,Master節點用bit來標識對於某個槽本身是否擁有。好比對於編號爲1的槽,Master只要判斷序列的第二位(索引從0開始)是否是爲1便可。分佈式
位序列spa
如上面的序列,表示當前Master擁有編號爲1,134的槽。集羣同時還維護着槽到集羣節點的映射,是由長度爲16384類型爲節點的數組實現的,槽編號爲數組的下標,數組內容爲集羣節點,這樣就能夠很快地經過槽編號找到負責這個槽的節點。位序列這個結構很精巧,即不浪費存儲空間,操做起來又很便捷。代理
這裏講的是Redis Cluster如何將鍵空間分佈在不一樣的節點的,鍵空間意爲Redis Cluster所擁有用戶全部數據集合的鍵的取值範圍,這個範圍叫作鍵空間。提到空間分佈,必然會想到哈希算法,沒錯,經過哈希算法再加上取模運算能夠將一個值固定地映射到某個區間,在這裏,這個區間叫作slots,區間由連續的slot組成。在Redis Cluster中,咱們擁有16384個slot,這個數是固定的,咱們存儲在Redis Cluster中的全部的鍵都會被映射到這些slot中,下面講講Redis Cluster是如何作映射的。code
鍵到slot的基本映射算法以下:索引
HASH_SLOT = CRC16(key) mod 16384
用Redis中的代碼表示以下(這個代碼被稍微修改了一下,後面會還原):
crc16(key) & 0x3FFF
通過簡單的計算就獲得了當前key應該是存儲在哪一個slot裏面,值得注意的是,指定的key會被存儲在哪一個slot,這個關係是鐵打不變的。若是我提交了一批命令,往Redis中存儲一批鍵,那麼這些鍵通常會被映射到不一樣的slot,而不一樣的slot又可能由Redis Cluster中不一樣的節點服務,這樣就和的預期有點不一樣,有沒有辦法將這批鍵映射到同一個slot呢?答案是能夠。
鍵哈希標籤是一種可讓用戶指定將一批鍵都可以被存放在同一個槽中的實現方法,用戶惟一要作的就是按照既定規則生成key便可,這個規則是這樣的,若是我有對於同一個用戶有兩種不一樣含義的兩份數據,我只要將他們的鍵設置爲下面便可:
abc{userId}def和ghi{userId}jkl
redis在計算槽編號的時候只會獲取{}之間的字符串進行槽編號計算,這樣因爲上面兩個不一樣的鍵,{}裏面的字符串是相同的,所以他們能夠被計算出相同的槽,相關代碼以下:
unsigned int keyHashSlot(char *key, int keylen) { int s, e; for (s = 0; s < keylen; s++) if (key[s] == '{') break; if (s == keylen) return crc16(key,keylen) & 0x3FFF; for (e = s+1; e < keylen; e++) if (key[e] == '}') break; if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; return crc16(key+s+1,e-s-1) & 0x3FFF; }
客戶端是怎麼在Redis Cluster中找到正確的節點的呢?下面看看。
文章開始講到,Redis Cluster並不會代理查詢,那麼若是客戶端訪問了一個key並不存在的節點,這個節點是怎麼處理的呢?好比我想獲取key爲msg的值,msg計算出來的槽編號爲254,當前節點正好不負責編號爲254的槽,那麼就會返回客戶端下面信息:
GET msg -MOVED 254 127.0.0.1:6381
表示客戶端想要的254槽由運行在IP爲127.0.0.1,端口爲6381的Master實例服務。若是根據key計算得出的槽剛好由當前節點負責,則當期節點會當即返回結果。這裏明確一下,沒有代理的Redis Cluster可能會致使客戶端兩次鏈接急羣中的節點才能找到正確的服務,推薦客戶端緩存鏈接,這樣最壞的狀況是兩次往返通訊。
從新分片意爲槽到集羣節點的映射關係要改變,不變的是鍵到槽的映射關係,所以當從新分片的時候,若是槽中有鍵,那麼鍵也是要被移動到新的節點的。下面看看從新分片是怎麼作的,假如咱們有一批槽須要從一個Master節點移動到另外一個Master節點:
槽遷移示意圖
這裏簡化模型,假設這批待遷移的槽編號爲一、二、3,並假設左邊的節點爲MasterA節點,右邊的節點爲MasterB節點。
槽遷移的過程當中有一個不穩定狀態,這個不穩定狀態會有一些規則,這些規則定義客戶端的行爲,從而使得Redis Cluster沒必要宕機的狀況下能夠執行槽的遷移。下面這張圖描述了咱們遷移編號爲一、二、3的槽的過程當中,他們在MasterA節點和Master節點中的狀態。槽一、二、3在MasterA節點中的狀態爲MIGRATING,在MasterB節點中的狀態爲IMPORTING。
槽遷移中間狀態
本例中MIGRATING狀態是發生在MasterA節點中的一種槽的狀態,預備遷移槽的時候槽的狀態首先會變爲MIGRATING狀態,這種狀態的槽會實際產生什麼影響呢?當客戶端請求的某個Key所屬的槽處於MIGRATING狀態的時候,影響有下面幾條:
本例中的IMPORTING狀態是發生在MasterB節點中的一種槽的狀態,預備將槽從MasterA節點遷移到MasterB節點的時候,槽的狀態會首先變爲IMPORTING。IMPORTING狀態的槽對客戶端的行爲有下面一些影響:
鍵空間遷移是指當知足了槽遷移前提的狀況下,咱們就能夠經過相關命令將槽一、二、3中的鍵空間從MasterA節點轉移到MasterB節點,這個過程真正實現數據轉移。相關命令:
MIGRATE
MIGRATE命令經過三步將數據轉移,示意圖以下:
鍵空間遷移步驟
通過上面三步能夠將鍵空間數據遷移,而後再將處於MIGRATING和IMPORTING狀態的槽變爲常態便可,完成整個從新分片的過程。然而MIGRATE並非原子的,若是在MIGRATE出現錯誤的狀況可能會致使下面問題:
本文介紹Redis Cluster分區實現原理主要關注三個問題,1)數據是如何被自動分散到不一樣的節點的;2)客戶端是如何可以正確找到節點的;3)鍵空間遷移過程是怎麼樣的?其次是一些實現小細節,經過了解這些問題更好地了這些問題從而更好地認識Redis Cluster分區功能。