在分布式系統路由分配上,一致性雜湊演算法有很大的優勢。在之前的文章中也講過原理。演算法容易理解,但是實現上要注意很多細節,虛節點加入也要消耗更多的儲存來維護對映關係。但是今天介紹的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 單調性是指如果已經有一些內容通過雜湊分派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的...