圖解一致性哈希算法,全網(小區局域網)最通俗易懂

不少同窗應該都知道什麼是哈希函數,在後端面試和開發中會遇到「一致性哈希」,那麼什麼是一致性哈希呢?名字聽起來很厲害的樣子,其實原理並不複雜,這篇文章帶你完全搞懂一致性哈希!node

進入主題前,先來一場緊張刺激的模擬面試吧。
模擬面試表情.pngnginx

模擬面試

面試官:看你簡歷上寫參與了一個大型項目,用到了分佈式緩存集羣,那你說說大家是怎麼作緩存負載均衡?c++

萌新 :這個我知道,咱們用的是輪詢方式,第一個key 給第一個存儲節點,第二個 key 給第二個,以此類推。面試

面試官:還有其餘解決方案嗎?算法

萌新:能夠用哈希函數,把請求打散隨機分配到緩存集羣內機器。shell

面試官:考慮過這種哈希方式負載均衡的擴展性和容錯性嗎?後端

萌新:...數組

面試官:回去等通知吧。緩存

以上若有雷同,算你抄個人。服務器

什麼是哈希

數據結構中咱們學習過哈希表也稱爲散列表,咱們來回顧下散列表的定義。

散列表,是根據鍵直接訪問在指定儲存位置數據的數據結構。經過計算一個關於鍵的函數也稱爲哈希函數,將所需查詢的數據映射到表中一個位置來訪問記錄,加快查找速度。這個映射函數稱作「散列函數」,存放記錄的數組稱作散列表。

散列函數能使對一個數據序列的訪問過程更加迅速有效,是一種空間換時間的算法,經過散列函數數據元素將被更快定位。

下圖示意了字符串通過哈希函數映射到哈希表的過程。沒錯,輸入字符串是用臉滾鍵盤打出來的:)

哈希示意圖.png

常見的哈希算法有MD五、CRC 、MurmurHash 等算法。

MD5算法

MD5消息摘要算法(英語:MD5 Message-Digest Algorithm),一種被普遍使用的密碼散列函數,能夠產生出一個128位(16字節)的散列值(hash value),MD5算法將數據(如一段文字)運算變爲另外一固定長度值,是散列算法的基礎原理。由美國密碼學家 Ronald Linn Rivest設計,於1992年公開並在 RFC 1321 中被加以規範。

CRC算法

循環冗餘校驗(Cyclic Redundancy Check)是一種根據網絡數據包或電腦文件等數據,產生簡短固定位數校驗碼的一種散列函數,由 W. Wesley Peterson 於1961年發表。生成的數字在傳輸或者存儲以前計算出來而且附加到數據後面,而後接收方進行檢驗肯定數據是否發生變化。因爲本函數易於用二進制的電腦硬件使用、容易進行數學分析而且尤爲善於檢測傳輸通道干擾引發的錯誤,所以得到普遍應用。

MurmurHash

MurmurHash 是一種非加密型哈希函數,適用於通常的哈希檢索操做。由 Austin Appleby 在2008年發明,並出現了多個變種,與其它流行的哈希函數相比,對於規律性較強的鍵,MurmurHash的隨機分佈特徵表現更良好。

這個算法已經被不少開源項目使用,好比libstdc++ (4.6版)、Perl、nginx (不早於1.0.1版)、Rubinius、 libmemcached、maatkit、Hadoop等。

常見散列方法

  • 直接定址法:取關鍵字或關鍵字的某個線性函數值爲散列地址,這個線性函數的定義多種多樣,沒有標準。
  • 數字分析法:假設關鍵字是以r爲基的數,而且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。
  • 平方取中法:取關鍵字平方後的中間幾位爲哈希地址。一般在選定哈希函數時不必定能知道關鍵字的所有狀況,取其中的哪幾位也不必定合適,而一個數平方後的中間幾位數和數的每一位都相關,由此使隨機分佈的關鍵字獲得的哈希地址也是隨機的,取的位數由表長決定。
  • 摺疊法:將關鍵字分割成位數相同的幾部分(最後一部分的位數能夠不一樣),而後取這幾部分的疊加和(捨去進位)做爲哈希地址。
  • 取模法:取關鍵字被某個不大於散列表表長 m 的數 p 除後所得的餘數爲散列地址。即 hash(key) = key % p(p<= M),不只能夠對關鍵字直接取模,也可在摺疊法、平方取中法等運算以後取模。對 p 的選擇很重要,通常取素數或 m,若 p 選擇很差,容易產生衝突。

緩存系統負載均衡

在分佈式集羣緩存的負載均衡實現中,好比 memcached 緩存集羣,須要把緩存數據的 key 利用哈希函數散列,這樣緩存數據可以均勻分佈到各個分佈式存儲節點上,要實現這樣的負載均衡通常能夠用哈希算法來實現。下圖演示了這一分佈式存儲過程:

分佈式緩存散列存儲示意圖

普通哈希算法負載均衡

前面咱們介紹過各類散列方法,無論是選擇上述哪一種散列方法,在這個應用場景下,都是要把緩存數據利用哈希函數均勻的映射到服務器集羣上,咱們就選擇簡單的「取模法」來講明這個過程。

假設有 3 個服務器節點編號 [0 - 2],6 個緩存鍵值對編號 [1 - 6],則完成哈希映射以後,三個緩存數據映射狀況以下:

哈希計算公式:key % 節點總數 = Hash節點下標
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
5 % 3 = 2
6 % 3 = 0

緩存哈希實例

每一個鏈接都均勻的分散到了三個不一樣的服務器節點上,看起來很完美!

可是,在分佈式集羣系統的負載均衡實現上,這種模型有兩個問題:

1. 擴展能力差

爲了動態調節服務能力,服務節點常常須要擴容縮容。打個比方,若是是電商服務,雙十一期間的服務機器數量確定要比日常大不少,新加進來的機器會使原來計算的哈希值不許確,爲了達到負載均衡的效果,要從新計算並更新哈希值,對於更新後哈希值不一致的緩存數據,要遷移到更新後的節點上去。

假設新增了 1 個服務器節點,由原來的 3 個服務節點變成 4 個節點編號 [0 - 3],哈希映射狀況以下:

哈希計算公式:key % 節點總數 = Hash節點下標
1 % 4 = 1
2 % 4 = 2
3 % 4 = 3
4 % 4 = 0
5 % 4 = 1
6 % 4 = 2

能夠看到後面三個緩存 key :四、五、6 對應的存儲節點所有失效了,這就須要把這幾個節點的緩存數據遷移到更新後的節點上 (費時費力) ,也就是由原來的節點 [1, 2, 0] 遷移到節點 [0, 1, 2],遷移後存儲示意圖以下:

緩存哈希擴展性示意圖

2. 容錯能力不佳

線上環境服務節點雖然有各類高可用性保證,但仍是是有宕機的可能,即便沒有宕機也有縮容的需求。無論是宕機和縮容均可以歸結爲服務節點刪除的狀況,下面分析下服務節點刪除對負載均衡哈希值的影響。

假設刪除 1 個服務器節點,由最初的 3 個服務節點變成 2 個,節點編號 [0 - 1],哈希映射狀況以下:

哈希計算公式:key % 節點總數 = Hash節點下標
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
4 % 2 = 0
5 % 2 = 1
6 % 2 = 0

下圖展現普通哈希負載均衡算法在一個節點宕機時候,致使的的緩存數據遷移分佈狀況:

緩存哈希容錯性示意圖

如圖所見,在這個例子中,僅僅刪除了一個服務節點,也致使了哈希值的大面積更新,哈希值的更新也是意味着節點緩存數據的遷移(緩存數據表示心好累)。

一致性哈希算法負載均衡

正是因爲普通哈希算法實現的緩存負載均衡存在擴展能力和容錯能力差問題,因此咱們引入一致性哈希算法,那麼什麼是一致性哈希呢?先來看下wiki上對一致性Hash的定義

一致哈希由 MIT 的 David Karger 及其合做者提出,如今這一思想已經擴展到其它領域。在這篇1997年發表的學術論文中介紹了一致哈希如何應用於用戶易變的分佈式Web服務中。一致哈希也可用於實現健壯緩存來減小大型Web應用中系統部分失效帶來的負面影響。

這篇描述一致性哈希的論文發表於1997年,閱讀無障礙的同窗能夠直接看看大佬的論文理解更深入,附上論文下載連接:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.147.1879

一致性hash論文

一句話歸納一致性哈希:就是普通取模哈希算法的改良版,哈希函數計算方法不變,只不過是經過構建環狀的 Hash 空間代替普通的線性 Hash 空間。具體作法以下:

首先,選擇一個足夠大的Hash空間(通常是 0 ~ 2^32)構成一個哈希環。

一致性哈希環

而後,對於緩存集羣內的每一個存儲服務器節點計算 Hash 值,能夠用服務器的 IP 或 主機名計算獲得哈希值,計算獲得的哈希值就是服務節點在 Hash 環上的位置。

節點哈希

最後,對每一個須要存儲的數據 key 一樣也計算一次哈希值,計算以後的哈希也映射到環上,數據存儲的位置是沿順時針的方向找到的環上的第一個節點。下圖舉例展現了節點存儲的數據狀況,咱們下面的說明也是基於目前的存儲狀況來展開。

原理講完了,來看看爲何這樣的設計能解決上面普通哈希的兩個問題

擴展能力提高

前面咱們分析過,普通哈希算法當須要擴容增長服務節點的時候,會致使原油哈希映射大面積失效。如今,咱們來看下一致性哈希是如何解決這個問題的。

以下圖所示,當緩存服務集羣要新增一個節點node3時,受影響的只有 key3 對應的數據 value3,此時只需把 value3 由原來的節點 node0 遷移到新增節點 node3 便可,其他節點存儲的數據保持不動

一致性哈希-擴展節點

容錯能力提高

普通哈希算法當某一服務節點宕機下線,也會致使原來哈希映射的大面積失效,失效的映射觸發數據遷移影響緩存服務性能,容錯能力不足。一塊兒來看下一致性哈希是如何提高容錯能力的。

以下圖所示,假設 node2 節點宕機下線,則原來存儲於 node2 的數據 value2 和 value5 ,只需按順時針方向選擇新的存儲節點 node0 存放便可,不會對其餘節點數據產生影響。一致性哈希能把節點宕機形成的影響控制在順時針相鄰節點之間,避免對整個集羣形成影響

一致性哈希-刪除節點

一致性哈希優化

存在的問題

上面展現了一致性哈希如何解決普通哈希的擴展和容錯問題,原理比較簡單,在理想狀況下能夠良好運行,但在實際使用中還有一些實際問題須要考慮,下面具體分析。

數據傾斜

試想一下若緩存集羣內的服務節點比較少,就像咱們例子中的三個節點,而哈希環的空間又有很大(通常是 0 ~ 2^32),這會致使什麼問題呢?

可能的一種狀況是,較少的服務節點哈希值彙集在一塊兒,好比下圖所示這種狀況 node0 、node一、node2 彙集在一塊兒,緩存數據的 key 哈希都映射到 node2 的順時針方向,數據按順時針尋找存儲節點就致使全都存儲到 node0 上去,給單個節點很大的壓力!這種狀況稱爲數據傾斜

一致性哈希-數據傾斜

節點雪崩

數據傾斜和節點宕機均可能會致使緩存雪崩。

雪崩

拿前面數據傾斜的示例來講,數據傾斜致使全部緩存數據都打到 node0 上面,有可能會致使 node0 不堪重負被壓垮了,node0 宕機,數據又都打到 node1 上面把 node1 也打垮了,node1 也被打趴傳遞給 node2,這時候故障就像像雪崩時滾雪球同樣越滾越大

還有一種狀況是節點因爲各類緣由宕機下線。好比下圖所示的節點 node2 下線致使本來在node2 的數據壓到 node0 , 在數據量特別大的狀況下也可能致使節點雪崩,具體過程就像剛纔的分析同樣。

總之,連鎖反應致使的整個緩存集羣不可用,就稱爲節點雪崩

一致性哈希-節點雪崩

虛擬節點

那該如何解決上述兩個棘手的問題呢?能夠經過「虛擬節點」的方式解決。

所謂虛擬節點,就是對原來單一的物理節點在哈希環上虛擬出幾個它的分身節點,這些分身節點稱爲「虛擬節點」。打到分身節點上的數據實際上也是映射到分身對應的物理節點上,這樣一個物理節點能夠經過虛擬節點的方式均勻分散在哈希環的各個部分,解決了數據傾斜問題

因爲虛擬節點分散在哈希環各個部分,當某個節點宕機下線,他所存儲的數據會被均勻分配給其餘各個節點,避免對單一節點突發壓力致使的節點雪崩問題。

下圖展現了虛擬節點的哈希環分佈,其中左邊是沒作虛擬節點狀況下的節點分佈,右邊背景色綠色兩個的 node0 節點是 node0 節點的虛擬節點;背景色紅色的 node1 節點是 node1 的虛擬節點。

一致性哈希-虛擬節點

總結一下

本文首先介紹了什麼是哈希算法和常見的哈希算法,以及常見散列方式,接着說明基於普通哈希算法的緩存負載均衡實現,並舉例說明普通算法的擴展性和容錯性方便存在的問題。

爲了解決普通算法的擴展性和容錯性問題引入一致性哈希算法,圖解和舉例分析了一致性哈希是如何提升擴展性和容錯性。最後粗糙的一致性哈希算法也存在數據傾斜和節點雪崩的問題,講解了如何利用虛擬節點優化一致性哈希算法,解決數據傾斜和雪崩問題。至此,一致性哈希你學會了嗎?

再聊兩句(求三連)

感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,咱們一塊兒在探討中學習。

若是以爲文章寫的還行,對你有所幫助,不要白票 lemon,動動手指點個「點贊」或「轉發」是對我持續創做的最大支持

今天的技術分享就到這裏,咱們下期再見。

歡迎關注下方我的技術公衆號「後端技術學堂」學習交流

公衆號二維碼.png

相關文章
相關標籤/搜索