Redis叢集資料沒法拆分時的搭建策略

等不到的口琴發表於2021-01-30

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

  • 單點故障
  • 容量有限
  • 可支援的連線有限(效能不足)

提出瞭解決的辦法:根據AKF原則搭建叢集,大意是先X軸拆分,建立單機的映象,組成主主、主備、主從模型,然後Y軸拆分,根據業務將不同的訪問分配在不同的業務Redis上,對於同一業務的Redis,如果還不足以支撐併發訪問,那麼可以繼續Z軸拆分,也就是根據資料拆分,詳細情況參照:Redis叢集拆分原則之AKF

結合上面提出一個新問題,如果一個服務,業務資料沒法拆分,或者說不容易拆分呢?也就是沒辦法沿著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節點可能操勞過度而死,隨後從節點選舉為主節點後,同樣當機,一次類推,造成快取雪崩。

相關文章