一致性hash演算法原理及go實現

g4zhuj發表於2018-04-25

概述

這裡存在一種場景, 當一個服務由多個伺服器組共同提供時, key應該路由到哪一個服務.這裡假如採用最通用的方式key%N(N為伺服器數目), 這裡乍一看沒什麼問題, 但是當伺服器數目傳送增加或減少時, 分配方式則變為key%(N+1)或key%(N-1).這裡將會有大量的key失效遷移,如果後端key對應的是有狀態的儲存資料,那麼毫無疑問,這種做法將導致伺服器間大量的資料遷移,從而照成服務的不穩定. 為了解決類問題,一致性hash演算法應運而生.

1. 一致性hash演算法特點

在分散式快取中, 一個好的hash演算法應該要滿足以下幾個條件:

  • 均衡性(Balance)
    均衡性主要指,通過演算法分配, 叢集中各節點應該要儘可能均衡.

  • 單調性(Monotonicity)
    單調性主要指當叢集發生變化時, 已經分配到老節點的key, 儘可能的任然分配到之前節點,以防止大量資料遷移, 這裡一般的hash取模就很難滿足這點,而一致性hash演算法能夠將發生遷移的key數量控制在較低的水平.

  • 分散性(Spread)
    分散性主要針對同一個key, 當在不同客戶端操作時,可能存在客戶端獲取到的快取叢集的數量不一致,從而導致將key對映到不同節點的問題,這會引發資料的不一致性.好的hash演算法應該要儘可能避免分散性.

  • 負載(Load)
    負載主要是針對一個快取而言, 同一快取有可能會被使用者對映到不同的key上,從而導致該快取的狀態不一致.

從原理來看,一致性hash演算法針對以上問題均有一個合理的解決.

2. 一致性hash詳解

一致性hash的核心思想為將key作hash運算, 並按一定規律取整得出0-2^32-1之間的值, 環的大小為2^32,key計算出來的整數值則為key在hash環上的位置,如何將一個key,對映到一個節點, 這裡分為兩步. 第一步, 將服務的key按該hash演算法計算,得到在服務在一致性hash環上的位置. 第二步, 將快取的key,用同樣的方法計算出hash環上的位置,按順時針方向,找到第一個大於等於該hash環位置的服務key,從而得到該key需要分配的伺服器。

圖片描述

如圖, 各key根據hash演算法分配到各節點,當某一節點失效實效時, 如NODE 2, 則NODE 2 上的key將分配到hash環上相鄰的節點,而其他key所在位置不變。

圖片描述

虛擬節點提高均衡性

如上圖可看到, 由於節點只有3個,存在某些節點所在位置周圍有大量的hash點從而導致分配到這些節點到key要比其他節點多的多,這樣會導致叢集中各節點負載不均衡,為解決這個問題,引入虛擬節點, 即一個實節點對應多個虛擬節點。快取的key作對映時,先找到對應的虛擬節點,再對應到實節點。如下圖所示, 每個節點虛擬出兩個虛擬節點,從而提高均衡性。

圖片描述

3. 一致性hash演算法與其他演算法對比

對於叢集中快取類資料key的節點分配問題,有這幾種解決方法,簡單的hash取模,槽對映,一致性hash。

  • hash取模
    對於hash取模,均衡性沒有什麼問題,但是如果叢集中新增一個節點時,將會有N/(N+1)的資料實效,當N值越大,失效率越高。這顯然是不可接受的。

  • 槽對映
    redis採用的就是這種演算法, 其思想是將key值做一定運算(如crc16, crc32,hash), 獲得一個整數值,再將該值與固定的槽數取模(slots), 每個節點處理固定的slots。獲取key所在的節點時,先要計算出key與槽的對應關係,再通過槽與節點的對應關係找到節點,這裡每次新增節點時,只需要遷移一定槽對應的key即可,而不遷移的槽點key值則不會實效,這種方式將實效率降低到了 1/(N+1)。不過這種方式有個缺點就是所有節點都需要知道槽與節點對應關係,如果client端不儲存槽與節點的對應關係的話,它需要實現重定向的邏輯。

  • 一致性hash
    一致性hash如上文所言,其新增一個節點的失效率僅為1/(N+1),通過一致性hash最大程度的降低了實效率。同時相比於槽對映的方式,不需要引人槽來做中間對應,最大限度的簡化了實現。

4. 基於golang的一致性hash演算法實現

這裡講採用golang實現一致性hash,考慮到實際使用場景中,存在服務節點之間機器配置可能不一樣,因此提供了基於節點權重進行虛擬節點再分配的邏輯,從而儘可能讓權重高的節點多承擔一些key,而權重低的節點少承擔一些key,當然這裡權重的計算也涉及到較多東西,程式碼見:hashring

5. 總結

本文分析了一致性性hash的原理,並與其它的分散式叢集分配演算法進行了對比,從分散式快取的角度來說,兩大出名的分佈儲存系統redis, memcached分別採用了槽對映,及一致性hash來實現,由於採用的演算法不同,叢集中節點變更時所觸發的一系列動作也不盡相同,各有各的考慮。

6.參考

Consistent Hasing
Redis Cluster

相關文章