一致性Hash算法
概念:先構(gòu)造一個長度為232的整數(shù)環(huán)(這個環(huán)被稱為一致性Hash環(huán)),根據(jù)節(jié)點名稱的Hash值(其分布為[0, 232-1])將服務器節(jié)點放置在這個Hash環(huán)上,然后根據(jù)數(shù)據(jù)的Key值計算得到其Hash值(其分布也為[0, 232-1]),接著在Hash環(huán)上順時針查找距離這個Key值的Hash值最近的服務器節(jié)點,完成Key到服務器的映射查找。
????????這種算法解決了普通余數(shù)Hash算法伸縮性差的問題,可以保證在上線、下線服務器的情況下盡量有多的請求命中原來路由到的服務器。
????????當然,萬事不可能十全十美,一致性Hash算法比普通的余數(shù)Hash算法更具有伸縮性,(普通的余數(shù)hash算法增加或者減少機器的時候容易造成大量緩存失效從而倒置雪崩)但是同時其算法實現(xiàn)也更為復雜,本文就來研究一下,如何利用Java代碼實現(xiàn)一致性Hash算法。在開始之前,先對一致性Hash算法中的幾個核心問題進行一些探究。
一致性hash算法的線上應用
? 首先我們來看下String的hashCode()的方法計算出來的hash值代碼如下:
? ? ? ? 代碼省略。
我們在做集群的時候,集群點的IP以這種連續(xù)的形式存在是很正常的??匆幌逻\行結(jié)果為:
????????運行結(jié)果省略。
hash值的區(qū)間基本大致是-2052839345--2012725363這樣的區(qū)間,并且有負數(shù)的存在,當然我們可以通過取絕對值的方法規(guī)避負數(shù)。最重要的是一致性hash的區(qū)間是[0,232-1]中有4294967296個數(shù)字,我們的hashcode方法的hash值遠遠小于一致性hash區(qū)間這樣會倒置數(shù)據(jù)嚴重的不勻均可能有的節(jié)點會存在百分之90的數(shù)據(jù)。綜上,String重寫的hashCode()方法在一致性Hash算法中沒有任何實用價值,得找個算法重新計算HashCode。這種重新計算Hash值的算法有很多,比如CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,其中KETAMA_HASH是默認的MemCache推薦的一致性Hash算法,用別的Hash算法也可以,比如FNV1_32_HASH算法的計算效率就會高一些。
一致性Hash算法實現(xiàn)版本1:不帶虛擬節(jié)點
使用一致性Hash算法,盡管增強了系統(tǒng)的伸縮性,但是也有可能導致負載分布不均勻,解決辦法就是使用虛擬節(jié)點代替真實節(jié)點,第一個代碼版本,先來個簡單的,不帶虛擬節(jié)點。想象一下一個環(huán)上只有三點節(jié)點,很容就分布的不均勻。
下面來看一下不帶虛擬節(jié)點的一致性Hash算法的Java代碼實現(xiàn):
運行結(jié)果如下:
? ? ? ?代碼省略具體的代碼實現(xiàn)在碼云上。
看到經(jīng)過FNV1_32_HASH算法重新計算過后的Hash值,就比原來String的hashCode()方法好多了。從運行結(jié)果來看,也沒有問題,三個點路由到的都是順時針離他們Hash值最近的那臺服務器上。最重要的是這樣的一致性算法可以解決擴展性差的問題,但是這樣的一致性算法是不能在生產(chǎn)環(huán)境使用的。因為這樣的算法是負載不均勻的。
使用虛擬節(jié)點來改善一致性Hash算法
?
比如說有Hash環(huán)上有A、B、C三個服務器節(jié)點,分別有100個請求會被路由到相應服務器上?,F(xiàn)在在A與B之間增加了一個節(jié)點D,這導致了原來會路由到B上的部分節(jié)點被路由到了D上,這樣A、C上被路由到的請求明顯多于B、D上的,原來三個服務器節(jié)點上均衡的負載被打破了。某種程度上來說,這失去了負載均衡的意義,因為負載均衡的目的本身就是為了使得目標服務器均分所有的請求。
解決這個問題的辦法是引入虛擬節(jié)點,其工作原理是:將一個物理節(jié)點拆分為多個虛擬節(jié)點,并且同一個物理節(jié)點的虛擬節(jié)點盡量均勻分布在Hash環(huán)上。采取這樣的方式,就可以有效地解決增加或減少節(jié)點時候的負載不均衡的問題。
想象一下我們把一個節(jié)點虛擬化成200個節(jié)點使他更加的分散在hash環(huán)上這樣我們就能解決負載不均衡的問題。
在理解了使用虛擬節(jié)點來改善一致性Hash算法的理論基礎之后,就可以嘗試開發(fā)代碼了。編程方面需要考慮的問題是:
一個真實結(jié)點如何對應成為多個虛擬節(jié)點?
虛擬節(jié)點找到后如何還原為真實結(jié)點?
至于一個真實結(jié)點對應分成多少個虛擬結(jié)點的問題,經(jīng)過大量的測試一般是150-200個虛擬結(jié)點會達到較好的效果,具體的實現(xiàn)還需要根據(jù)真實節(jié)點去測試。那么虛擬節(jié)點可以為真實節(jié)點+后綴名的方式實現(xiàn)那么還原也比較容易。
下面代碼實現(xiàn)如下:
代碼省略具體的代碼實現(xiàn)在碼云上。
運行代碼可以查找到虛擬節(jié)點的hash值,求出來數(shù)據(jù)的key的hash值來找到對應的節(jié)點。這樣我們就能解決了擴展差和負載差的問題。