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


在 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 支持我們!

微信交流群

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM