Redis集羣數據無法拆分時的搭建策略

在上一篇文章中,針對服務器單點、單例、單機存在的問題:html

  • 單點故障
  • 容量有限
  • 可支持的鏈接有限(性能不足)

提出瞭解決的辦法:根據AKF原則搭建集羣,大意是先X軸拆分,建立單機的鏡像,組成主主、主備、主從模型,而後Y軸拆分,根據業務將不一樣的訪問分配在不一樣的業務Redis上,對於同一業務的Redis,若是還不足以支撐併發訪問,那麼能夠繼續Z軸拆分,也就是根據數據拆分,詳細狀況參照:Redis集羣拆分原則之AKFredis

結合上面提出一個新問題,若是一個服務,業務數據無法拆分,或者說不容易拆分呢?也就是沒辦法沿着Y-Z軸拆解,那麼如何集羣呢?算法

解決思路:數據庫

一致性Hash緩存

哈希槽安全

一致性Hash

說到一致性Hash,就得先搞明白爲何要有一致性Hash,介紹一致性Hash算法以前,先簡單回顧一下分佈式以及Hash算法,便於理解爲何要有一致性Hash算法。服務器

分佈式

當咱們也無需求很複雜時,單臺機器IO以及帶寬等都會成爲瓶頸,因此對業務進行拆分,部署在不一樣的機器上,當有請求訪問時,根據某些特色將這些請求分散到各個服務器上,這全部的服務器組成的網絡,咱們稱之爲集羣,這能提升服務器的性能以及利用率。網絡

若是有一個請求過來存數據,調度器將數據存在了A服務器上,下次客戶端再來請求這份數據,該如何迅速判斷數據存在哪臺服務器呢?這時候引出了哈希的概念。併發

哈希函數

對於一個函數:分佈式

\[y = f(x) \]

若是兩個哈希值是不相同的,那麼這兩個哈希值的原始輸入也是不相同的。這個特性是哈希函數具備肯定性的結果,具備這種性質的哈希函數稱爲單向哈希函數。

但另外一方面,哈希函數的輸入和輸出不是惟一對應關係的,若是兩個哈希值相同,兩個輸入值極可能是相同的,但也可能不一樣,若是值相同稱爲「哈希碰撞(collision)」,這一般是兩個不一樣長度的輸入值,刻意計算出相同的輸出值。輸入一些數據計算出哈希值,而後部分改變輸入值,一個具備強混淆特性的哈希函數會產生一個徹底不一樣的哈希值。哈希函數是不可逆的,也就是無法根據哈希值來反推輸入值。

那麼對於集羣而言,每個請求都有一個標識ID,若是構造標識到服務器的哈希函數,就可讓同一請求固定的達到服務器,由於ID是不變的:

\[Server_{id} = hash (Request_{id},N) \]

爲了均勻分佈在不一樣的服務器上,這是一個跟服務器數量N有關的哈希函數,常見的有取模運算,即:

\[Server_{id} = hash (Request_{id})\%N \]

可是這時候有個問題,因爲是集羣,那麼服務器數量可能會有變化,例現在天請求量很是大要增長服務器數量,N變大以後,原有的Hash值就失效了,須要從新對存儲的請求值作哈希,因爲請求值是一個龐大的數據集.這樣形成了巨大的不便,須要改進咱們的Hash算法。這時候引出了一致性Hash的思路。

一致性Hash

一致性哈希的思路是,將哈希函數與服務器數量解綁,若是咱們用16個二進制標識哈希值,那麼哈希值有一個範圍區間\([0,65536]\),咱們將這個區間65536平均分紅N份,N是服務器數量,而後將這些服務器節點等距離安排在一個圓環上

假設服務器數量是4,那麼對於全部請求,哈希值一定是介於\([0,65536]\)之間,當節點計算哈希後對應環上某一個點,這時候順時針尋找離本身最近的服務節點做爲存儲節點,例如:當請求哈希值介於\([0,16384]\)之間時,將請求分配到Server 1,若是請求哈希值介於\((16384,32768]\)之間時,將請求分配到Server 2,後面同理,這樣作有一個什麼好處呢,若是這時候須要新增長一個服務器,假設是Server 5:

那麼須要從新計算Hash值的僅僅爲\([0,8192]\)這部分請求,將其分配到Server 5上。

這地方有兩點要注意,不少博客都沒有提到:

一是原有節點信息本應該歸server 2 存儲,新增長了Server 5 以後根據新的規則會映射到Server 5上,那麼以前存儲在Server 1 上的數據須要從新取出來放在Server 5 上嗎?

答案是否認的,只須要查找數據,在最近的點沒找到,往下(順時針)再查找一個點就好了,也能夠再查找兩個點(防止插入了兩臺新的服務節點)

二是,刪除某個服務器節點

如圖,刪除服務器Server 4 以後,會將原有分佈在Server4 上的請求全都壓在Server1上,若是Server1 hold不住,那麼可能掛掉,掛掉以後數據又轉移給Server2,如此循環會形成全部節點崩潰,也就是雪崩的狀況。這種雪崩能夠靠下面的虛擬節點引入解決

虛擬節點的引入

咱們已經知道,添加和刪除節點都會影響緩存數據的分佈。儘管hash算法具備分佈均勻的特性,可是當集羣中server數量不多時,他們可能在環中的分佈並非特別均勻,進而致使緩存數據不能均勻分佈到全部的server上,例如都分配在某一個或幾個哈希區間,那麼有不少服務器可能就沒在這個分佈式系統中提供做用。

爲解決這個問題,須要使用虛擬節點, 虛擬節點的思想:爲每一個物理節點(server)在環上分配100~200個點,這樣環上的節點較多,就能抑制分佈不均勻。當爲cache定位目標server時,若是定位到虛擬節點上,就表示cache真正的存儲位置是在該虛擬節點表明的實際物理server上。另外,若是每一個實際server節點的負載能力不一樣,能夠賦予不一樣的權重,根據權重分配不一樣數量的虛擬節點。定位算法不變,只是多了一步虛擬節點到真實節點映射的過程

爲何說這樣能解決雪崩的發生呢,由於即便一臺服務器掛了,他應對的虛擬節點也會消失,那也就是新來的數據依舊可在原區間內隨機或者根據某種規律分配到其餘節點上。

注意:真實節點不放置到哈希環上,只有虛擬節點纔會放上去。

另外無法作數據庫用(待補充)

哈希槽

瞭解哈希槽前景知識仍是普通哈希,前面講了哈希存在的問題是,當服務節點變化時,假如是增長到N+1,要對全部數據從新對服務器數量N+1取模,這在分佈式中是難以接受的,太浪費時間。

那麼,可不能夠先假設我未來會有不少機器,例如一萬臺,對一萬臺取模,而後構建映射將取模值(哈希值)分區呢?Redis 集羣的哈希槽就是這種思路:

用咱們的Key值,對12取模,那麼範圍是在\([0,11]\)之間,這兒的\([0,11]\)就是咱們所說的槽值,而後將這個範圍區間切割成N份,N爲服務器數量,如圖:

以後若是新增了Redis服務器,只須要將映射的槽值對應的數據移動去新服務器就好了:

基於哈希槽的cluster集羣

當客戶端來獲取數據時,隨便鏈接到哪個服務器上,服務器內置上面說的Hash算法,對請求Id進行Hash獲得哈希值,每個服務器內置一個表,這個表將哈希值對應的哈希槽與數據存儲服務節點作一個映射,也就是請求壓到任一臺服務器,有數據的話會返回數據,沒有的話會返回存儲所需數據的服務器節點位置:

哈希槽與一致性哈希區別

它並非閉合的,key的定位規則是根據CRC-16(key)%16384的值來判斷屬於哪一個槽區,從而判斷該key屬於哪一個節點,而一致性哈希是根據hash(key)的值來順時針找第一個hash(ip)的節點,從而肯定key存儲在哪一個節點。

一致性哈希是建立虛擬節點來實現節點宕機後的數據轉移並保證數據的安全性和集羣的可用性的。redis cluster是採用master節點有多個slave節點機制來保證數據的完整性的,master節點寫入數據,slave節點同步數據。當master節點掛機後,slave節點會經過選舉機制選舉出一個節點變成master節點,實現高可用。可是這裏有一點須要考慮,若是master節點存在熱點緩存,某一個時刻某個key的訪問急劇增高,這時該mater節點可能操勞過分而死,隨後從節點選舉爲主節點後,一樣宕機,一次類推,形成緩存雪崩。

相關文章
相關標籤/搜索