一文搞懂一致性 hash 的原理和實現

kevwan發表於2021-07-20

在 go-zero 的分散式快取系統分享裡,Kevin 重點講到過一致性 hash 的原理和分散式快取中的實踐。本文來詳細講講一致性 hash 的原理和在 go-zero 中的實現。

以儲存為例,在整個微服務系統中,我們的儲存不可能說只是一個單節點。

  • 一是為了提高穩定,單節點當機情況下,整個儲存就面臨服務不可用;
  • 二是資料容錯,同樣單節點資料物理損毀,而多節點情況下,節點有備份,除非互為備份的節點同時損毀。

那麼問題來了,多節點情況下,資料應該寫入哪個節點呢?

hash

所以本質來講:我們需要一個可以將輸入值 “壓縮” 並轉成更小的值,這個值通常狀況下是唯一、格式極其緊湊的,比如 uint64

  • 冪等:每次用同一個值去計算 hash 必須保證都能得到同一個值

這個就是 hash 演算法完成的。

但是採取普通的 hash 演算法進行路由,如:key % N 。有一個節點由於異常退出了叢集或者是心跳異常,這時再進行 hash route ,會造成大量的資料重新 分發到不同的節點 。節點在接受新的請求時候,需要重新處理獲取資料的邏輯:如果是在快取中,容易引起 快取雪崩

此時就需要引入 consistent hash 演算法了。

consistent hash

我們來看看 consistent hash 是怎麼解決這些問題的:

rehash

先解決大量 rehash 的問題:

如上圖,當加入一個新的節點時,影響的 key 只有 key31,新加入(剔除)節點後,只會影響該節點附近的資料。其他節點的資料不會收到影響,從而解決了節點變化的問題。

這個正是:單調性。這也是 normal hash 演算法無法滿足分散式場景的原因。

資料傾斜

其實上圖可以看出:目前多數的 key 都集中在 node 1 上。如果當 node 數量比較少的情況下,可以回引發多數 key 集中在某個 node 上,監控時發現的問題就是:節點之間負載不均。

為了解決這個問題,consistent hash 引入了 virtual node 的概念。

既然是負載不均,我們就人為地構造一個均衡的場景出來,但是實際 node 只有這麼多。所以就使用 virtual node 劃分割槽域,而實際服務的節點依然是之前的 node。

具體實現

先來看看 Get()

Get

先說說實現的原理:

  1. 計算 key 的 hash
  2. 找到第一個匹配的 virtual node 的 index,並取到對應的 h.keys[index] :virtual node hash 值
  3. 對應到這個 ring 中去尋找一個與之匹配的 actual node

其實我們可以看到 ring 中獲取到的是一個 []node 。這是因為在計算 virtual node hash ,可能會發生 hash 衝突,不同的 virtual node hash 對應到一個實際 node。

這也說明:nodevirtual node 是一對多的關係。而裡面的 ring 就是下面這個設計:

這個其實也就表明了一致性 hash 的分配策略:

  1. virtual node 作為值域劃分。key 去獲取 node ,從劃分依據上是以 virtual node 作為邊界
  2. virtual node 通過 hash ,在對應關係上保證了不同的 node 分配的 key 是大致均勻的。也就是 打散繫結
  3. 加入一個新的 node,會對應分配多個 virtual node。新節點可以負載多個原有節點的壓力,從全域性看,較容易實現擴容時的負載均衡。

Add Node

看完 Get 其實大致就知道整個一致性 hash 的設計:

type ConsistentHash struct {
  hashFunc Func                         // hash 函式
  replicas int                          // 虛擬節點放大因子
  keys     []uint64                 // 儲存虛擬節點hash
  ring     map[uint64][]interface{}                 // 虛擬節點與實際node的對應關係
  nodes    map[string]lang.PlaceholderType  // 實際節點儲存【便於快速查詢,所以使用map】
  lock     sync.RWMutex
}

好了這樣,基本的一個一致性 hash 就實現完備了。

具體程式碼:https://github.com/tal-tech/go-zero/blob/master/core/hash/consistenthash.go

使用場景

開頭其實就說了,一致性 hash 可以廣泛使用在分散式系統中:

  1. 分散式快取。可以在 redis cluster 這種儲存系統上構建一個 cache proxy,自由控制路由。而這個路由規則就可以使用一致性 hash 演算法
  2. 服務發現
  3. 分散式排程任務

以上這些分散式系統中,都可以在負載均衡模組中使用。

專案地址

https://github.com/tal-tech/go-zero

歡迎使用 go-zero 並 star 支援我們!

微信交流群

關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。

更多原創文章乾貨分享,請關注公眾號
  • 一文搞懂一致性 hash 的原理和實現
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章