這裏存在一種場景, 當一個服務由多個服務器組共同提供時, key應該路由到哪個服務.這裏假如採用最通用的方式key%N(N爲服務器數目), 這裏乍一看沒什麼問題, 可是當服務器數目發送增長或減小時, 分配方式則變爲key%(N+1)或key%(N-1).這裏將會有大量的key失效遷移,若是後端key對應的是有狀態的存儲數據,那麼毫無疑問,這種作法將致使服務器間大量的數據遷移,從而照成服務的不穩定. 爲了解決類問題,一致性hash算法應運而生.git
在分佈式緩存中, 一個好的hash算法應該要知足如下幾個條件:github
均衡性主要指,經過算法分配, 集羣中各節點應該要儘量均衡.golang
單調性主要指當集羣發生變化時, 已經分配到老節點的key, 儘量的任然分配到以前節點,以防止大量數據遷移, 這裏通常的hash取模就很難知足這點,而一致性hash算法可以將發生遷移的key數量控制在較低的水平.redis
分散性主要針對同一個key, 當在不一樣客戶端操做時,可能存在客戶端獲取到的緩存集羣的數量不一致,從而致使將key映射到不一樣節點的問題,這會引起數據的不一致性.好的hash算法應該要儘量避免分散性.算法
負載主要是針對一個緩存而言, 同一緩存有可能會被用戶映射到不一樣的key上,從而致使該緩存的狀態不一致.後端
從原理來看,一致性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所在位置不變。分佈式
虛擬節點提升均衡性memcached
如上圖可看到, 因爲節點只有3個,存在某些節點所在位置周圍有大量的hash點從而致使分配到這些節點到key要比其餘節點多的多,這樣會致使集羣中各節點負載不均衡,爲解決這個問題,引入虛擬節點, 即一個實節點對應多個虛擬節點。緩存的key做映射時,先找到對應的虛擬節點,再對應到實節點。以下圖所示, 每一個節點虛擬出兩個虛擬節點,從而提升均衡性。
對於集羣中緩存類數據key的節點分配問題,有這幾種解決方法,簡單的hash取模,槽映射,一致性hash。
對於hash取模,均衡性沒有什麼問題,可是若是集羣中新增一個節點時,將會有N/(N+1)的數據實效,當N值越大,失效率越高。這顯然是不可接受的。
redis採用的就是這種算法, 其思想是將key值作必定運算(如crc16, crc32,hash), 得到一個整數值,再將該值與固定的槽數取模(slots), 每一個節點處理固定的slots。獲取key所在的節點時,先要計算出key與槽的對應關係,再經過槽與節點的對應關係找到節點,這裏每次新增節點時,只須要遷移必定槽對應的key便可,而不遷移的槽點key值則不會實效,這種方式將失效率下降到了 1/(N+1)。不過這種方式有個缺點就是全部節點都須要知道槽與節點對應關係,若是client端不保存槽與節點的對應關係的話,它須要實現重定向的邏輯。
一致性hash如上文所言,其新增一個節點的失效率僅爲1/(N+1),經過一致性hash最大程度的下降了實效率。同時相比於槽映射的方式,不須要引人槽來作中間對應,最大限度的簡化了實現。
這裏講採用golang實現一致性hash,考慮到實際使用場景中,存在服務節點之間機器配置可能不同,所以提供了基於節點權重進行虛擬節點再分配的邏輯,從而儘量讓權重高的節點多承擔一些key,而權重低的節點少承擔一些key,固然這裏權重的計算也涉及到較多東西,代碼見:
https://github.com/g4zhuj/has...
本文分析了一致性性hash的原理,並與其它的分佈式集羣分配算法進行了對比,從分佈式緩存的角度來講,兩大出名的分佈存儲系統redis, memcached分別採用了槽映射,及一致性hash來實現,因爲採用的算法不一樣,集羣中節點變動時所觸發的一系列動做也不盡相同,各有各的考慮。
Consistent Hasing https://en.wikipedia.org/wiki...
Redis Cluster https://redis.io/topics/clust...