一種快速簡潔的一致性雜湊演算法

2021-09-13 20:27:38 字數 3594 閱讀 6033

在分布式系統路由分配上,一致性雜湊演算法有很大的優勢。在之前的文章中也講過原理。演算法容易理解,但是實現上要注意很多細節,虛節點加入也要消耗更多的儲存來維護對映關係。但是今天介紹的jump consistent hash是一種比較新穎的方法,**簡短,記憶體消耗少。下面我們來詳細看看這個演算法。

首先我們先了解下這個演算法,有個初步的概念。然後看下這個演算法適用於哪些場景,有什麼作用。最後,詳細分析下演算法原理。

int32_t jumpconsistenthash(uint64_t key, int32_t num_buckets) 

return b;

}

以上就是演算法的全部**,輸入引數分別是64位的key,桶的數量(一般對應伺服器的數量),輸出是乙個桶的編號(從0開始)。

滿足演算法的要求:

平衡性,把物件均勻地分布在所有桶中。

單調性,當桶的數量變化時,只需要把一些物件從舊桶移動到新桶,不需要做其它移動。

用於分布式儲存產品中,而不太適合用在快取型別的產品。因為有節點不可用時,jumphash用存活節點分擔不可用節點的能力不強,會導致分布不均。但是在儲存類中,節點都會有主備,主節點不可用路由到備節點,key的分布不會有變化。

適合在分布式系統中,根據key來選擇被分配到的服務場景。每次新增新的服務節點,只有1/n的key會變動,不會因為擴容或縮容瞬間

造成大部分快取失效。

但是也有侷限,和其他的一致性hash相比。如果有中間的桶失效,是不能夠像割環hash一樣,均勻分配到其他節點的,只能找個新替換

節點來取代。但是優點是不用儲存,計算量也不大。**短,易於實現。

利用線性同餘計算的固定性,每次輸入引數固定,輸出就固定的特性,來替代用儲存。利用運算,減少儲存空間。

由於運算量的優化,比查詢儲存空間速度更快,所以從時間、空間上演算法更優。

引申:有時用運算生成的數字串,對映要儲存的空間,會使演算法有意想不到的效果。

為什麼上面的**能夠實現一致性hash的功能,我們一步一步來看。要實現的功能就是多加乙個節點,節點數變為n,只有1/n的key會變動。

我們先構造乙個函式,

ch(key, num_buckets) 

表示有num_buckets個桶,乙個key的值會分配到的bucket編號[0, num_buckets)。

所以對於任意key,k,ch(k,1)=0,因為只有乙個桶。為了讓演算法平衡,ch(k,2)講有一半的key留在0號桶中,一半的移到1號桶中。

總結的規律是,ch(k,n+1)和ch(k,n)相比,n/(n+1)的key是不動的,1/(n+1)的key移動到第n號桶。

對於每次新增桶的個數時,計算每個key的新位置,確定是否要移動到新的桶中。

通過隨機數生成器,來判定key是否要移動到新的桶中,概率是1/(n+1)要移動。

int ch(int key, int num_buckets) 

return b;

}//**中的random.next()產生[0,1)的隨機數,隨機數序列只和key有關,key為隨機種子。

這段**是滿足演算法的平衡性和單調性的,演算法複雜度是o(n)。滿足了正確性,接下來優化效能。

從演算法**可以看出,大多數情況下random.next() < 1.0/(j+1)是不被執行的。

對於乙個key來說,ch(key,j+1)的值,很少會隨著j增長而變化的。當ch(key,j+1)!=ch(key,j)時,

ch(key,j+1)=j。

//我們假設ch(key,j)是乙個隨機變數,通過偽隨機數,來確定乙個數值b,當j增長到b時,ch(key,b)!=ch(key,b-1),

並且ch(key,j)=ch(key,b-1)。

假設乙個key的值為k,b為乙個跳變的桶數量。則ch(k,b)!=ch(k,b+1),並且ch(k,b+1)=b.

下面尋找下乙個比b大的跳變的桶數量j。則ch(k,j+1)!=ch(k,j),ch(k,j)=b,ch(k,j+1)=j。

有ch(k,b+1)=b

ch(k,j)=b,

ch(k,j)=ch(k,b+1)

ch(k,j+1)=j

ch(k,b)!=ch(k,b+1)

ch(k,j+1)!=ch(k,j)

所以,我們已知k,b時,要找到j,對於(b,j]區間的變數i,如果不發生跳變,必須滿足

ch(k,i)=ch(k,b+1)。

所以有概率

p(j>=i) = p(ch(k,i)=ch(k,b+1))

先舉幾個例子p(ch(k,10)=ch(k,11))的概率是10/11,

p(ch(k,11)=ch(k,12))的概率是11/12,

所以p(ch(k,10)=ch(k,12))的概率是p(ch(k,10)=ch(k,11))*p(ch(k,11)=ch(k,12))=(10/11)*(11/12)=10/12

對於任意的n>=m,p(ch(k,n)=ch(k,m))=m/n。

所以對於上面的等式,

p(j>=i) = p(ch(k,i)=ch(k,b+1))=(b+1)/i。

假設乙個隨機數r在(0,1)區間,由k和j確定。

如果r<=(b+1)/i,那麼p(j>=i)=(b+1)/i為不跳變。

那麼產生隨機數r後,就能確定i的最小值為(b+1)/r。

因為r<=(b+1)/i => i<=(b+1)/r.

又因為i是整數,所以有

r!=0

i=floor((b+1)/r)

**可改寫為

int ch(int key, int num_buckets) 

return = b;

}

假設r的期望為0.5,時間複雜度為olg(n)。

這個演算法有點繞,通過隨機數的產生來判定下一跳的j,優化演算法,保證在整體key的跳變滿足增加桶數為n+1時,只有1/(n+1)的資料移動。

我們再看

key = key * 2862933555777941757ull + 1;

j = (b + 1) * (double(1ll << 31) / double((key >> 33) + 1));

和r = random.next();

j = floor((b + 1) / r);

有什麼關係。

利用線性同餘演算法產生乙個64位的整數,然後通過對映到(0,1]區間的小數。

(key>>33)+1是取key值的高31位的值再加1,範圍為(1,2^31+1)

1ll<<31的值為2^31。

所以[(key>>33)+1]/1ll<<31 的取值範圍是(0,1],如果(key>>33)=2^31那麼會大於1,由於是c的整數運算,大於1也會取證忽略掉小數部分。

該演算法的精髓:通過隨機種子產生隨機數,減少儲存;利用概率和隨機數,確定key在bucket_num範圍內落在的桶序號。

既減少了運算量,也易於實現,對於儲存類路由非常適合,而且key的分散性不依賴key本身,只依賴隨即生成器,對key的要求不高,不用做轉換。

參考:

一種快速簡潔的一致性雜湊演算法

一致性雜湊演算法

好吧,我們決定打破這種基於資料項商業邏輯的劃分思維,來考慮一種基於 key 的劃分方式,這有些類似於後面介紹的資料庫水平分割槽 sharding 我們需要設計一種不依賴資料項內容的雜湊演算法,將所有資料項的 key 均衡分配在這三颱快取伺服器上。乙個簡單而有效的方法是 取餘 運算,這就像打撲克時的發...

一致性雜湊演算法

在分布式系統中,如果某業務可以由多個相同的節點處理,很容易想到用hash的方式將業務請求分散到這些節點處理,如果有n個節點,計算方法為 hash id n。如果只是簡單的計算,不涉及使用者狀態,這是乙個簡單有效的方案。如果節點的計算涉及使用者狀態,比如維護購物車 memcache快取服務等,好像也沒...

一致性雜湊演算法

判定好壞的四個定義 1 平衡性 balance 平衡性是指雜湊的結果能夠盡可能分布到所有的緩衝中去,這樣可以使得所有的緩衝空間都得到利用。很多雜湊演算法都能夠滿足這一條件。2 單調性 monotonicity 單調性是指如果已經有一些內容通過雜湊分派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的...