圖解一致性hash算法和實現

更多內容,歡迎關注微信公衆號:全菜工程師小輝。公衆號回覆關鍵詞,領取免費學習資料。node

一致性hash算法是什麼?

一致性hash算法,是麻省理工學院1997年提出的一種算法,目前主要應用於分佈式緩存當中。
一致性hash算法能夠有效地解決分佈式存儲結構下動態增長和刪除節點所帶來的問題。
在Memcached、Key-Value Store、Bittorrent DHT、LVS中都採用了一致性hash算法,能夠說一致性hash算法是分佈式系統負載均衡的首選算法。算法

傳統hash算法的弊端

經常使用的算法是對hash結果取餘數 (hash() mod N):對機器編號從0到N-1,按照自定義的hash算法,對每一個請求的hash值按N取模,獲得餘數i,而後將請求分發到編號爲i的機器。但這樣的算法方法存在致命問題,若是某一臺機器宕機,那麼應該落在該機器的請求就沒法獲得正確的處理,這時須要將宕掉的服務器使用算法去除,此時候會有(N-1)/N的服務器的緩存數據須要從新進行計算;若是新增一臺機器,會有N /(N+1)的服務器的緩存數據須要進行從新計算。對於系統而言,這一般是不可接受的顛簸(由於這意味着大量緩存的失效或者數據須要轉移)。
緩存

傳統求餘作負載均衡算法,緩存節點數由3個變成4個,緩存不命中率爲75%。計算方法:窮舉hash值爲1-12的12個數字分別對3和4取模,而後比較發現只有前3個緩存節點對應結果和以前相同,因此有75%的節點緩存會失效,可能會引發緩存雪崩。服務器

一致性hash算法

  1. 首先,咱們將hash算法的值域映射成一個具備232 次方個桶的空間中,即0~(232)-1的數字空間。如今咱們能夠將這些數字頭尾相連,組合成一個閉合的環形。
  2. 每個緩存key均可以經過Hash算法轉化爲一個32位的二進制數,也就對應着環形空間的某一個緩存區。咱們把全部的緩存key映射到環形空間的不一樣位置。
  3. 咱們的每個緩存節點也遵循一樣的Hash算法,好比利用IP或者主機名作Hash,映射到環形空間當中,以下圖

image

  1. 如何讓key和緩存節點對應起來呢?很簡單,每個key的順時針方向最近節點,就是key所歸屬的緩存節點。因此圖中key1存儲於node1,key2,key3存儲於node2,key4存儲於node3。

image

  1. 當緩存的節點有增長或刪除的時候,一致性哈希的優點就顯現出來了。讓咱們來看看實現的細節:
  • 增長節點
    當緩存集羣的節點有所增長的時候,整個環形空間的映射仍然會保持一致性哈希的順時針規則,因此有一小部分key的歸屬會受到影響。

image

有哪些key會受到影響呢?圖中加入了新節點node4,處於node1和node2之間,按照順時針規則,從node1到node4之間的緩存再也不歸屬於node2,而是歸屬於新節點node4。所以受影響的key只有key2。微信

image

最終把key2的緩存數據從node2遷移到node4,就造成了新的符合一致性哈希規則的緩存結構。markdown

  • 刪除節點
    當緩存集羣的節點須要刪除的時候(好比節點掛掉),整個環形空間的映射一樣會保持一致性哈希的順時針規則,一樣有一小部分key的歸屬會受到影響。

image

有哪些key會受到影響呢?圖中刪除了原節點node3,按照順時針規則,本來node3所擁有的緩存數據就須要「託付」給node3的順時針後繼節點node1。所以受影響的key只有key4。數據結構

image

最終把key4的緩存數據從node3遷移到node1,就造成了新的符合一致性哈希規則的緩存結構。負載均衡

說明:這裏所說的遷移並非直接的數據遷移,而是在查找時去找順時針的後繼節點,因緩存未命中而刷新緩存。分佈式

計算方法:假設節點hash散列均勻(因爲hash是散列表,因此並非很理想),採用一致性hash算法,緩存節點從3個增長到4個時,會有0-33%的緩存失效,此外新增節點不會環節全部原有節點的壓力。函數

一致性hash算法的結果相比傳統hash求餘算法已經進步不少,但可不能夠改進一下呢?或者若是出現分佈不均勻的狀況怎麼辦?好比下圖這樣,按順時針規則,全部的key都歸屬於統一個節點。

image

一致性hash算法+虛擬節點

爲了優化這種節點太少而產生的不均衡狀況。一致性哈希算法引入了虛擬節點的概念。
所謂虛擬節點,就是基於原來的物理節點映射出N個子節點,最後把全部的子節點映射到環形空間上。

image

虛擬節點越多,分佈越均勻。使用一致性hash算法+虛擬節點這種狀況下,緩存節點從3個變成4個,緩存失效率爲25%,並且每一個節點都平均的承擔了壓力。

一致性hash算法+虛擬節點的實現

原理理解了,實現並不難,主要是一些細節:

  1. hash算法的選擇。Java代碼不要使用hashcode函數,這個函數結果不夠散列,並且會有負值須要處理。
    這種計算Hash值的算法有不少,好比CRC32HASH、FNV132HASH、KETAMAHASH等,其中KETAMAHASH是默認的MemCache推薦的一致性Hash算法,用別的Hash算法也能夠,好比FNV132_HASH算法的計算效率就會高一些。
  2. 數據結構的選擇。根據算法原理,咱們的算法有幾個要求:
  • 要能根據hash值排序存儲
  • 排序存儲要被快速查找 (List不行)
  • 排序查找還要能方便變動 (Array不行)

另外,因爲二叉樹可能極度不平衡。因此採用紅黑樹是最穩妥的實現方法。Java中直接使用TreeMap便可。

更多內容,歡迎關注微信公衆號:全菜工程師小輝。公衆號回覆關鍵詞,領取免費學習資料。

哎呀,若是個人名片丟了。微信搜索「全菜工程師小輝」,依然能夠找到我

相關文章
相關標籤/搜索