一致性Hash算法Java版實(shí)現(xiàn)

本文已被Github倉庫收錄 https://github.com/silently9527/JavaCore

微信公眾號:貝塔學(xué)Java

前言

在之前寫了兩篇關(guān)于緩存的文章《萬字長文聊緩存(上)- http緩存》《萬字長文聊緩存(下)- 應(yīng)用級緩存》疹瘦,談到緩存不說一下一致性Hash算法那就是在耍流氓。

分布式緩存集群的訪問模型

現(xiàn)在通常使用Redis來做分布式緩存喂分,下面我們就以Redis為例:

image

假如當(dāng)前我們系統(tǒng)的業(yè)務(wù)發(fā)展很快模暗,需要緩存的數(shù)據(jù)很多剿吻,所以我們做了一個由三組主從復(fù)制的redis組成的高可用的redis集群,如何將請求路由的不同的redis集群上客情,這是我們需要考慮的溺森,常用的路由算法:

隨機(jī)算法:每次將請求隨機(jī)的發(fā)送到其中一組Redis集群中,這種算法的好處是請求會被均勻的分發(fā)到每組Redis集群上淘邻;缺點(diǎn)也很明顯茵典,由于隨機(jī)分發(fā)請求,為了提高緩存的命中率宾舅,所以同一份數(shù)據(jù)需要在每組集群中都存在统阿,這樣就會造成了數(shù)據(jù)的冗余,浪費(fèi)了存儲空間

Hash算法:針對隨機(jī)算法的問題贴浙,我們可以考慮Hash算法砂吞,舉例:
現(xiàn)在有三組redis集群,我們可以對每次緩存key的hash值取模崎溃,公式:index=hash(key) % 3蜻直,index的值就對應(yīng)著3組集群,這樣就可以保證同一個請求每次都被分發(fā)到同一個redis集群上袁串,無需對數(shù)據(jù)做冗余概而,完美的解決了剛才隨機(jī)算法的缺點(diǎn);

image

但是hash算法也有缺點(diǎn):對于容錯性和伸縮性支持很差囱修,舉例:當(dāng)我們?nèi)Mredis集群中其中一組節(jié)點(diǎn)宕機(jī)了赎瑰,那么此時的redis集群中可用的數(shù)量變成了2,公式變成了index=hash(key) % 2破镰, 所有數(shù)據(jù)緩存的節(jié)點(diǎn)位置就發(fā)生了變化餐曼,造成緩存的命中率直線下降;

同理鲜漩,當(dāng)我們需要擴(kuò)展一組新的redis機(jī)器源譬,計算的公式index=hash(key) % 4,大量的key會被重新定位到其他服務(wù)器孕似,也會造成緩存的命中率下降踩娘。

為了解決hash算法容錯性和伸縮性的問題,一致性hash算法由此而生~

一致性哈希算法

具體的算法過程

  1. 先構(gòu)造一個長度為2^32-1的整數(shù)環(huán)(稱為一致性hash環(huán))喉祭,然后給每組redis集群命名养渴,根據(jù)名字的hash值計算出每組集群應(yīng)該放在什么位置
image
  1. 根據(jù)緩存數(shù)據(jù)的key計算出hash值,計算出出來的hash值同樣也分布在一致性hash環(huán)上; 假如現(xiàn)在有5個數(shù)據(jù)需要緩存對應(yīng)的key分別為key1泛烙、key2理卑、key3、key4蔽氨、key5傻工,計算hash值之后的分部如下圖
image
  1. 然后順著hash環(huán)順時針方向查找reids集群,把數(shù)據(jù)存放到最近的集群上
image

最后所有key4孵滞、key5存放在了集群2中捆,key1、key3存放在了集群1坊饶,key2存放在了集群3

容錯性

還是繼續(xù)沿用上面的例子泄伪,我們來看下一致性哈希算法的容錯性如何呢?假如其中 集群1 跪了匿级,那么影響的數(shù)據(jù)只有key1和key3蟋滴,其他數(shù)據(jù)存放的位置不受影響;當(dāng)再次緩存key1痘绎、key3的時候根據(jù)順時針查找津函,會把數(shù)據(jù)存放到集群3上面

伸縮性

如果我們需要在當(dāng)前的基礎(chǔ)上再添加一組redis集群4,根據(jù)名字hash之后的位置在集群1和集群2之間

image

新加redis集群4之后影響的只有key1數(shù)據(jù)孤页,其他數(shù)據(jù)不受影響尔苦。

數(shù)據(jù)傾斜問題

經(jīng)過容錯性、伸縮性的驗(yàn)證證明了一致性哈希算法確實(shí)能解決Hash算法的問題行施,但是現(xiàn)在的算法就是完美的嗎允坚?讓我們繼續(xù)來看剛才容錯性的例子,加入集群1跪了蛾号,那么原來落在集群1上的所有數(shù)據(jù)會直接落在集群3上面稠项,如果說每組redis集群的配置都是一樣的,那么集群3的壓力會增大鲜结,數(shù)據(jù)分布不均勻造成數(shù)據(jù)傾斜問題展运。

image

怎么搞呢?

歪果仁的腦子就是好使精刷,給的解決方案就是加一層虛擬層拗胜,假如每組集群都分配了2個虛擬節(jié)點(diǎn)

集群 虛擬節(jié)點(diǎn)
集群1 vnode1, vnode2
集群2 vnode3, vnode4
集群3 vnode5, vnode6

接下來就是把虛擬節(jié)點(diǎn)放入到一致性hash環(huán)上,在緩存數(shù)據(jù)的時候根據(jù)順時針查找虛擬節(jié)點(diǎn)贬养,在根據(jù)虛擬節(jié)點(diǎn)的和實(shí)際集群的對應(yīng)關(guān)系把數(shù)據(jù)存放到redis集群挤土,這樣數(shù)據(jù)就會均勻的分布到各組集群中。

image

這時候如果有一組redis集群出現(xiàn)了問題误算,那么這組集群上面的key會相對均勻的分?jǐn)偟狡渌荷稀?/p>

從上面的結(jié)果來看仰美,只要每組集群對應(yīng)的虛擬節(jié)點(diǎn)越多,那么各個物理集群的數(shù)據(jù)分布越均勻儿礼,當(dāng)新增加或者減少物理集群影響也會最小咖杂,但是如果虛擬節(jié)點(diǎn)太多會影響查找的性能,太少數(shù)據(jù)又會不均勻蚊夫,那么多少合適呢诉字?根據(jù)一些大神的經(jīng)驗(yàn)給出的建議是 150 個虛擬節(jié)點(diǎn)。

一致性Hash算法Java版實(shí)現(xiàn)

實(shí)現(xiàn)思路:在每次添加物理節(jié)點(diǎn)的時候,根據(jù)物理節(jié)點(diǎn)的名字生成虛擬節(jié)點(diǎn)的名字壤圃,把虛擬節(jié)點(diǎn)的名字求hash值陵霉,然后把hash值作為key,物理節(jié)點(diǎn)作為value存放到Map中伍绳;這里我們選擇使用TreeMap踊挠,因?yàn)樾枰猭ey是順序的存儲;在計算數(shù)據(jù)key需要存放到哪個物理節(jié)點(diǎn)時冲杀,先計算出key的hash值效床,然后調(diào)用TreeMap.tailMap()返回比hash值大的map子集,如果子集為空就需要把TreeMap的第一個元素返回权谁,如果不為空剩檀,那么取子集中的第一個元素。

> 不扯廢話旺芽,直接上代碼沪猴,No BB . Show me the code

核心代碼:

image
image

測試代碼:

image
  1. 測試刪除節(jié)點(diǎn)node3,對比命中率影響了多少 添加如下代碼:
image

執(zhí)行結(jié)果:

image
  1. 測試添加節(jié)點(diǎn)node5甥绿,對比命中率影響了多少 添加如下代碼:
image

執(zhí)行結(jié)果:

image

其他使用場景

image

看上圖字币,在Nginx請求的分發(fā)過程中,為了讓應(yīng)用本地的緩存命中率最高共缕,我們希望根據(jù)請求的URL或者URL參數(shù)將相同的請求轉(zhuǎn)發(fā)到同一個應(yīng)用服務(wù)器中洗出,這個時候也可以選擇使用一致性hash算法。具體配置可以參考官方文檔:
https://www.nginx.com/resources/wiki/modules/consistent_hash/

image

寫到最后(點(diǎn)關(guān)注图谷,不迷路)

文中或許會存在或多或少的不足翩活、錯誤之處,有建議或者意見也非常歡迎大家在評論交流便贵。

最后菠镇,白嫖不好,創(chuàng)作不易承璃,希望朋友們可以點(diǎn)贊評論關(guān)注三連利耍,因?yàn)檫@些就是我分享的全部動力來源??

原創(chuàng)不易 轉(zhuǎn)載請注明出處:https://mp.weixin.qq.com/s/eCxGPqrfIeFY_E_CnFRfMw

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盔粹,隨后出現(xiàn)的幾起案子隘梨,更是在濱河造成了極大的恐慌,老刑警劉巖舷嗡,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轴猎,死亡現(xiàn)場離奇詭異,居然都是意外死亡进萄,警方通過查閱死者的電腦和手機(jī)捻脖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門锐峭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人可婶,你說我怎么就攤上這事沿癞。” “怎么了扰肌?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵抛寝,是天一觀的道長。 經(jīng)常有香客問我曙旭,道長,這世上最難降的妖魔是什么晶府? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任桂躏,我火速辦了婚禮,結(jié)果婚禮上川陆,老公的妹妹穿的比我還像新娘剂习。我一直安慰自己,他們只是感情好较沪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布鳞绕。 她就那樣靜靜地躺著,像睡著了一般尸曼。 火紅的嫁衣襯著肌膚如雪们何。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天控轿,我揣著相機(jī)與錄音冤竹,去河邊找鬼。 笑死茬射,一個胖子當(dāng)著我的面吹牛鹦蠕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播在抛,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼钟病,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刚梭?” 一聲冷哼從身側(cè)響起肠阱,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎望浩,沒想到半個月后辖所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磨德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年缘回,在試婚紗的時候發(fā)現(xiàn)自己被綠了吆视。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡酥宴,死狀恐怖啦吧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拙寡,我是刑警寧澤授滓,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站肆糕,受9級特大地震影響般堆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诚啃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一淮摔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧始赎,春花似錦和橙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至五辽,卻和暖如春办斑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奔脐。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工俄周, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人髓迎。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓峦朗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親排龄。 傳聞我的和親對象是個殘疾皇子波势,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容