Java一致性Hash算法的实现
哈希hash
hash的意思是散列,目的是將一組輸入的數(shù)據(jù)均勻的分開、打散,往往用來配合路由算法做負(fù)載均衡,多用在分布式系統(tǒng)中。比如memcached,它只提供了K V的存儲(chǔ)、讀取,如果使用了多臺(tái)memcache做一個(gè)“邏輯集群”,就需要客戶端做“路由算法”,來保證數(shù)據(jù)均勻的進(jìn)去,然后能“原路”拿出來。
常規(guī)哈希取模
常規(guī)哈希,往往結(jié)合取模運(yùn)算,以便將請求轉(zhuǎn)發(fā)到后端的服務(wù)器上,如下圖:
第一步使用hash算法,將請求“打散”得到一個(gè)整數(shù)(比如傳遞過來一個(gè)請求,使用jdk類庫的hash對某個(gè)參數(shù)做計(jì)算),第二步將得到的參數(shù)對后端的服務(wù)器臺(tái)數(shù)取模,以上圖為例,假設(shè)有三臺(tái)服務(wù)器,那么id分別為1~6的請求會(huì)被轉(zhuǎn)發(fā)到1,2,0,1,2,0,上,不管請求id數(shù)是多少,總是這么周而復(fù)始的轉(zhuǎn)發(fā)。
假設(shè)上面是個(gè)緩存系統(tǒng),以上請求為set請求,在服務(wù)器數(shù)量不變的情況下,對同樣的id做get請求,由于采用同樣的hash算法,那么肯定能原路找到對應(yīng)的key值。這個(gè)算法簡單,而且數(shù)據(jù)分散的比較均勻。
如果系統(tǒng)訪問量突增,為了擴(kuò)容加了一臺(tái)機(jī)器,編號(hào)為3,此時(shí)有了4臺(tái)機(jī)器,采用同樣的算法再去get請求會(huì)如何?比如id=6,這個(gè)時(shí)候 6%4=2,我們知道set時(shí)值其實(shí)放進(jìn)了索引為0的機(jī)器,這個(gè)時(shí)候就get不到了。這就是上面算法的弊端,在增減機(jī)器時(shí)會(huì)使舊的數(shù)據(jù)大量“失效”,也就是命中率下降。
不帶虛擬節(jié)點(diǎn)的一致性哈希算法
為了解決以上問題,聰明的人發(fā)明了一致性哈希算法。思路是這樣,hash算法出來的整數(shù)有個(gè)范圍,我們在這個(gè)范圍內(nèi)布置三臺(tái)服務(wù)器(范圍具體是多少看前面的hash算法)。假設(shè)hash的范圍是1~300,每臺(tái)負(fù)責(zé)一段范圍內(nèi)的請求,比如一臺(tái)負(fù)責(zé)(1~100],一臺(tái)負(fù)責(zé)(100~200],一臺(tái)負(fù)責(zé)(200~1]。這三臺(tái)server首尾相接覆蓋/閉環(huán)了所有請求,稱為哈希環(huán),如下圖:
如何實(shí)現(xiàn)一臺(tái)服務(wù)器接收一個(gè)范圍的請求?這個(gè)時(shí)候不用取模了,而是將server也按照hash算法計(jì)算一個(gè)id值,比如按照他們的ip+port+name拼成的串計(jì)算,假設(shè)正好分別是 1,100,200,將他們放進(jìn)一個(gè)treeMap里,Map<Integer,Node> ,其中Node代表server節(jié)點(diǎn),是自定義的數(shù)據(jù)結(jié)構(gòu),比如是一個(gè)類,包含ip,port,name等屬性。我們的例子中,map里包含三個(gè)元素。
一個(gè)請求過來,hash得到的值必屬于這三個(gè)server的范圍,比如一個(gè)請求id=N,那么從map里get(N)去找server,找到直接轉(zhuǎn)發(fā),找不到進(jìn)行如下運(yùn)算:treemap里有個(gè)關(guān)鍵的api,tailMap(),這個(gè)接口能夠返回id比N大的map的子集,然后取子集的第一個(gè)節(jié)點(diǎn),就是id=100的節(jié)點(diǎn),通常稱為順時(shí)針查找。
? ?
//得到應(yīng)當(dāng)路由到的結(jié)點(diǎn)(示例代碼用String代表的節(jié)點(diǎn))private static String getServer(String key) {//得到該key的hash值int hash = getHash(key);//得到大于該Hash值的所有MapSortedMap<Integer, String> subMap = sortedMap.tailMap(hash);if(subMap.isEmpty()){//如果沒有比該key的hash值大的,則從第一個(gè)node開始Integer i = sortedMap.firstKey();//返回對應(yīng)的服務(wù)器return sortedMap.get(i);}else{//第一個(gè)Key就是順時(shí)針過去離node最近的那個(gè)結(jié)點(diǎn)Integer i = subMap.firstKey();//返回對應(yīng)的服務(wù)器return subMap.get(i);}}當(dāng)然如果子集為空,這意味著N>200,就取整個(gè)map的第一個(gè)節(jié)點(diǎn),完成閉環(huán)。
分析:從實(shí)現(xiàn)可以看出,如果一個(gè)節(jié)點(diǎn)掛了,他的流量會(huì)順時(shí)針(逆時(shí)針實(shí)現(xiàn)也是一樣的)“導(dǎo)流”到下一個(gè)節(jié)點(diǎn),其他節(jié)點(diǎn)不受影響。假如有100臺(tái)服務(wù)器,一臺(tái)掛了,其他99臺(tái)都能正常命中!這個(gè)算法比簡單的取模好了很多。
不過這里仍有個(gè)問題,假設(shè)各臺(tái)服務(wù)器性能差不多,此時(shí)流量突增,一臺(tái)server由于流量過載而掛掉,那么它的下一臺(tái)因?yàn)槌休d了2倍的流量,很有可能也會(huì)掛掉,依此類推,最后所有的節(jié)點(diǎn)都會(huì)掛掉,造成“雪崩”!
因此正常情況下,我們往往采用帶虛擬節(jié)點(diǎn)的一致性哈希算法(不特別說明的一致性哈希算法一般都是指的帶虛擬節(jié)點(diǎn)的算法)。
帶虛擬節(jié)點(diǎn)的一致性哈希算法
帶虛擬節(jié)點(diǎn)的一致性哈希算法是為了解決不帶虛擬節(jié)點(diǎn)算法的雪崩問題,虛擬節(jié)點(diǎn)也稱為分片。在上一步的基礎(chǔ)上理解虛擬節(jié)點(diǎn)是非常容易的。“虛擬”節(jié)點(diǎn)是server的副本、分身,每個(gè)虛擬節(jié)點(diǎn)存儲(chǔ)的server信息還是后面的物理地址,只不過每個(gè)server由一臺(tái)變成了多臺(tái),這個(gè)時(shí)候往treeMap放節(jié)點(diǎn)時(shí)往往這么做:
for(i=1 ?--> ?N) // N為每個(gè)server對應(yīng)的分片數(shù)量 {Map.put(hash(ip+port+name+i),node) // 所有虛擬節(jié)點(diǎn)放進(jìn)去 }這個(gè)for循環(huán)外面還會(huì)有個(gè)循環(huán),處理所有server node
由于每個(gè)server的ip,name不同,所以以上拼串hash后的值碰撞的概率是很小的,這樣所有的虛擬節(jié)點(diǎn)也會(huì)離散的分布到環(huán)上,形成的hash環(huán)如下圖,同樣顏色的虛擬節(jié)點(diǎn)同屬于一個(gè)server。
這個(gè)時(shí)候如果紅顏色的server掛了,它的虛擬節(jié)點(diǎn)負(fù)責(zé)的范圍會(huì)分別導(dǎo)航到下一個(gè)虛擬節(jié)點(diǎn)上,這些虛擬節(jié)點(diǎn)分別屬于不同的server,就避免了流量全部導(dǎo)流到一臺(tái)機(jī)器上。由于流量被均攤了,有效的減少了雪崩發(fā)生的概率。(理論上仍存在虛擬節(jié)點(diǎn)后面的虛擬節(jié)點(diǎn)屬于同一個(gè)server的情況,但是當(dāng)虛擬節(jié)點(diǎn)非常多時(shí),這個(gè)概率是非常小的,而且這個(gè)分片數(shù)量是自定義的,往往設(shè)置幾百個(gè))。
只要是hash算法,就有哈希碰撞的可能性,在增加server時(shí),計(jì)算后的虛擬節(jié)點(diǎn)跟其他server的虛擬節(jié)點(diǎn)重復(fù)的話,也會(huì)導(dǎo)致部分緩存失效(可以通過算法改良)。
綜上,一致性哈希算法并不是強(qiáng)一致性,也不是高可用方案,如果server掛了數(shù)據(jù)丟了就是丟了,除非有恢復(fù)手段,它只是一種減少由擴(kuò)縮容引起的命中率下降的手段。
代碼可參考如下鏈接
https://blog.csdn.net/WANGYAN9110/article/details/70185652
https://blog.csdn.net/u010558660/article/details/52767218
————————————————
版權(quán)聲明:本文為CSDN博主「飛出銀河系」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/flyfeifei66/article/details/82458618
總結(jié)
以上是生活随笔為你收集整理的Java一致性Hash算法的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微服务为什么一定要Zookeeper?
- 下一篇: 你写的 Java 代码是如何一步步输出结