算法簡介
普通Hash算法
普通的Hash函數作用是散列,本質上就是將一系列在形式上具有相似性質的數據,打散成隨機的、均勻分布的數據。
比如,對字符進行md5計算,得到一串的hash值。
package main
import (
"crypto/md5"
"fmt"
)
func main() {
s1 := "abcd"
date := []byte(s1)
hs := md5.Sum(date)
md5str := fmt.Sprintf("%x", hs)
fmt.Println(md5str)
}
輸出:e2fc714c4727ee9395f324cd2e7f331f
一致性Hash算法(Consistent Hashing)
一致性哈希算法在1997年由麻省理工學院的Karger等人在解決分布式緩存中提出的,設計目標是為了解決因特網中的熱點(Hot spot)問題,
核心思想:將key作hash運算, 并按一定規律取整得出0 -
$2^{32}-1$
之間的值, 環的大小為 $2^{32}$
,key計算出來的整數值則為key在hash環上的位置。
主要用途:解決分布式系統中對象與節點的映射關系。
go語言算法實現
導入代碼實現庫
go get github.com/g4zhuj/hashring
使用例子:
// virtualSpots means virtual spots created by each node
nodeWeight := make(map[string]int)
nodeWeight["node1"] = 1
nodeWeight["node2"] = 1
nodeWeight["node3"] = 2
vitualSpots := 100
hash := NewHashRing(virtualSpots)
//add nodes
hash.AddNodes(nodeWeight)
//remove node
hash.RemoveNode("node3")
//add node
hash.AddNode("node3", 3)
//get key's node
node := hash.GetNode("key")
原理及使用場景
使用場景 分布式系統中對象與節點的映射關系。
傳統方案(硬哈希)是使用對象的哈希值,對節點個數取模,再映射到相應編號的節點,即 mod(key, n)。 這種方案在節點個數變動時,映射關系變為 mod(key, n+1) / mod(key, n-1),絕大多數對象的映射關系會失效而需要遷移。
詳細原理
一、創建哈希環
- 一致性哈希將整個哈希值空間組織成一個虛擬的圓環,如假設某哈希函數H的值空間為
$0-2^{32}-1$
(即哈希值是一個32位無符號整形),整個哈希空間環如下:
上圖中,可見整個空間按順時針方向組織,0和$2^{32}-1$
在零點中方向重合。
- 下一步,將各個服務器節點使用Hash進行一個哈希,具體可以選擇服務器的ip或主機名作為關鍵字進行哈希,這樣每臺機器就能確定其在哈希環上的位置,這里假設將上文中四臺服務器使用ip地址哈希后在環空間的位置如下:
- 接下來,使用如下算法定位數據訪問到相應服務器:將數據key使用相同的函數Hash計算出哈希值,并確定此數據在環上的位置,從此位置沿環順時針“行走”,第一臺遇到的服務器就是其應該定位到的服務器。
例如:我們有Object A、Object B、Object C、Object D四個數據對象,經過哈希計算后,在環空間上的位置如下:
根據一致性哈希算法,數據A會被定為到Node A上,B被定為到Node B上,C被定為到Node C上,D被定為到Node D上。
二、容錯性
下面分析一致性哈希算法的容錯性:
現假設Node C不幸宕機,可以看到此時對象A、B、D不會受到影響,只有C對象被重定位到Node D。
一般的,在一致性哈希算法中,如果一臺服務器不可用,則受影響的數據僅僅是此服務器到其環空間中前一臺服務器(即沿著逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。
三、擴張性
下面考慮另外一種情況,如果在系統中增加一臺服務器Node X,如下圖所示:
此時對象Object A、B、D不受影響,只有對象C需要重定位到新的Node X 。
一般的,在一致性哈希算法中,如果增加一臺服務器,則受影響的數據僅僅是新服務器到其環空間中前一臺服務器(即沿著逆時針方向行走遇到的第一臺服務器)之間數據,其它數據也不會受到影響。
綜上所述,一致性哈希算法對于節點的增減都只需重定位環空間中的一小部分數據,具有較好的容錯性和可擴展性。
四、可能問題及解決方式
一致性哈希算法在服務節點太少時,容易因為節點分部不均勻而造成數據傾斜問題。
例如系統中只有兩臺服務器,其環分布如下
此時必然造成大量數據集中到Node A上,而只有極少量會定位到Node B上。
為了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱為虛擬節點。
具體做法:在服務器ip或主機名的后面增加編號來實現。例如上面的情況,可以為每臺服務器計算三個虛擬節點,于是可以分別計算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六個虛擬節點:
同時數據定位算法不變,只是多了一步虛擬節點到實際節點的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三個虛擬節點的數據均定位到Node A上。這樣就解決了服務節點少時數據傾斜的問題。在實際應用中,通常將虛擬節點數設置為32甚至更大,因此即使很少的服務節點也能做到相對均勻的數據分布。
算法特點
- 均衡性(Balance)
均衡性主要指,通過算法分配, 集群中各節點應該要盡可能均衡。
- 單調性(Monotonicity)
單調性主要指,當集群發生變化時, 已經分配到老節點的key,盡可能的任然分配到之前節點,以防止大量數據遷移。
這里一般的hash取模就很難滿足這點,而一致性hash算法能夠將發生遷移的key數量控制在較低的水平。
- 分散性(Spread)
分散性主要針對同一個key,當在不同客戶端操作時,可能存在客戶端獲取到的緩存集群的數量不一致,從而導致將key映射到不同節點的問題,這會引發數據的不一致性。
好的hash算法應該要盡可能避免分散性。
- 負載(Load)
負載主要是針對一個緩存而言,同一緩存有可能會被用戶映射到不同的key上,從而導致該緩存的狀態不一致。
具體的工程應用場景
- Apache Cassandra:在數據分區(Data partitioning)中使用到一致性哈希
- Voldemort:LinkedIn 開發的鍵值(Key-Value)存儲數據庫,在數據分區(Data partitioning)中使用到一致性哈希
- Akka:Akka 的 Router 使用了一致性哈希
- Dynamo:數據劃分使用了一致性哈希
- Couchbase:在數據分區(Data partitioning)中使用到一致性哈希
- Riak:數據劃分使用了一致性哈希
- GlusterFS:文件分布使用了一致性哈希
參考文章