很多同學(xué)應(yīng)該都知道什么是哈希函數(shù),在后端面試和開發(fā)中會遇到「一致性哈希」,那么什么是一致性哈希呢?名字聽起來很厲害的樣子,其實(shí)原理并不復(fù)雜,這篇文章帶你徹底搞懂一致性哈希! 進(jìn)入主題前,先來一場緊張刺激的模擬面試吧。 模擬面試面試官:看你簡歷上寫參與了一個(gè)大型項(xiàng)目,用到了分布式緩存集群,那你說說你們是怎么做緩存負(fù)載均衡? 萌新 :這個(gè)我知道,我們用的是輪詢方式,第一個(gè)key 給第一個(gè)存儲節(jié)點(diǎn),第二個(gè) key 給第二個(gè),以此類推。 面試官:還有其他解決方案嗎? 萌新:可以用哈希函數(shù),把請求打散隨機(jī)分配到緩存集群內(nèi)機(jī)器。 面試官:考慮過這種哈希方式負(fù)載均衡的擴(kuò)展性和容錯(cuò)性嗎? 萌新:... 面試官:回去等通知吧。 以上如有雷同,算你抄我的。 什么是哈希數(shù)據(jù)結(jié)構(gòu)中我們學(xué)習(xí)過哈希表也稱為散列表,我們來回顧下散列表的定義。 散列表,是根據(jù)鍵直接訪問在指定儲存位置數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。通過計(jì)算一個(gè)關(guān)于鍵的函數(shù)也稱為哈希函數(shù),將所需查詢的數(shù)據(jù)映射到表中一個(gè)位置來訪問記錄,加快查找速度。這個(gè)映射函數(shù)稱做「散列函數(shù)」,存放記錄的數(shù)組稱做散列表。 散列函數(shù)能使對一個(gè)數(shù)據(jù)序列的訪問過程更加迅速有效,是一種空間換時(shí)間的算法,通過散列函數(shù)數(shù)據(jù)元素將被更快定位。 下圖示意了字符串經(jīng)過哈希函數(shù)映射到哈希表的過程。沒錯(cuò),輸入字符串是用臉滾鍵盤打出來的:) 哈希示意圖.png 常見的哈希算法有MD5、CRC 、MurmurHash 等算法,簡單介紹一下。 MD5算法MD5消息摘要算法(MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數(shù),可以產(chǎn)生出一個(gè)128位(16字節(jié))的散列值(hash value),MD5算法將數(shù)據(jù)(如一段文字)運(yùn)算變?yōu)榱硪还潭ㄩL度值,是散列算法的基礎(chǔ)原理。由美國密碼學(xué)家 Ronald Linn Rivest設(shè)計(jì),于1992年公開并在 RFC 1321 中被加以規(guī)范。 CRC算法循環(huán)冗余校驗(yàn)(Cyclic Redundancy Check)是一種根據(jù)網(wǎng)絡(luò)數(shù)據(jù)包或電腦文件等數(shù)據(jù),產(chǎn)生簡短固定位數(shù)校驗(yàn)碼的一種散列函數(shù),由 W. Wesley Peterson 于1961年發(fā)表。生成的數(shù)字在傳輸或者存儲之前計(jì)算出來并且附加到數(shù)據(jù)后面,然后接收方進(jìn)行檢驗(yàn)確定數(shù)據(jù)是否發(fā)生變化。由于本函數(shù)易于用二進(jìn)制的電腦硬件使用、容易進(jìn)行數(shù)學(xué)分析并且尤其善于檢測傳輸通道干擾引起的錯(cuò)誤,因此獲得廣泛應(yīng)用。 MurmurHashMurmurHash 是一種非加密型哈希函數(shù),適用于一般的哈希檢索操作。由 Austin Appleby 在2008年發(fā)明,并出現(xiàn)了多個(gè)變種,與其它流行的哈希函數(shù)相比,對于規(guī)律性較強(qiáng)的鍵,MurmurHash的隨機(jī)分布特征表現(xiàn)更良好。 這個(gè)算法已經(jīng)被很多開源項(xiàng)目使用,比如libstdc++ (4.6版)、Perl、nginx (不早于1.0.1版)、Rubinius、 libmemcached、maatkit、Hadoop等。 常見散列方法
緩存系統(tǒng)負(fù)載均衡在分布式集群緩存的負(fù)載均衡實(shí)現(xiàn)中,比如 memcached 緩存集群,需要把緩存數(shù)據(jù)的 key 利用哈希函數(shù)散列,這樣緩存數(shù)據(jù)能夠均勻分布到各個(gè)分布式存儲節(jié)點(diǎn)上,要實(shí)現(xiàn)這樣的負(fù)載均衡一般可以用哈希算法來實(shí)現(xiàn)。下圖演示了這一分布式存儲過程: 分布式緩存散列存儲示意圖 普通哈希算法負(fù)載均衡前面我們介紹過各種散列方法,不管是選擇上述哪種散列方法,在這個(gè)應(yīng)用場景下,都是要把緩存數(shù)據(jù)利用哈希函數(shù)均勻的映射到服務(wù)器集群上,我們就選擇簡單的「取模法」來說明這個(gè)過程。 假設(shè)有 3 個(gè)服務(wù)器節(jié)點(diǎn)編號 [0 - 2],6 個(gè)緩存鍵值對編號 [1 - 6],則完成哈希映射之后,三個(gè)緩存數(shù)據(jù)映射情況如下: 哈希計(jì)算公式:key % 節(jié)點(diǎn)總數(shù) = Hash節(jié)點(diǎn)下標(biāo) 緩存哈希實(shí)例 每個(gè)連接都均勻的分散到了三個(gè)不同的服務(wù)器節(jié)點(diǎn)上,看起來很完美! 但是,在分布式集群系統(tǒng)的負(fù)載均衡實(shí)現(xiàn)上,這種模型有兩個(gè)問題: 1. 擴(kuò)展能力差為了動態(tài)調(diào)節(jié)服務(wù)能力,服務(wù)節(jié)點(diǎn)經(jīng)常需要擴(kuò)容縮容。打個(gè)比方,如果是電商服務(wù),雙十一期間的服務(wù)機(jī)器數(shù)量肯定要比平常大很多,新加進(jìn)來的機(jī)器會使原來計(jì)算的哈希值不準(zhǔn)確,為了達(dá)到負(fù)載均衡的效果,要重新計(jì)算并更新哈希值,對于更新后哈希值不一致的緩存數(shù)據(jù),要遷移到更新后的節(jié)點(diǎn)上去。 假設(shè)新增了 1 個(gè)服務(wù)器節(jié)點(diǎn),由原來的 3 個(gè)服務(wù)節(jié)點(diǎn)變成 4 個(gè)節(jié)點(diǎn)編號 [0 - 3],哈希映射情況如下:
緩存哈希擴(kuò)展性示意圖 2. 容錯(cuò)能力不佳線上環(huán)境服務(wù)節(jié)點(diǎn)雖然有各種高可用性保證,但還是是有宕機(jī)的可能,即使沒有宕機(jī)也有縮容的需求。不管是宕機(jī)和縮容都可以歸結(jié)為服務(wù)節(jié)點(diǎn)刪除的情況,下面分析下服務(wù)節(jié)點(diǎn)刪除對負(fù)載均衡哈希值的影響。 假設(shè)刪除 1 個(gè)服務(wù)器節(jié)點(diǎn),由最初的 3 個(gè)服務(wù)節(jié)點(diǎn)變成 2 個(gè),節(jié)點(diǎn)編號 [0 - 1],哈希映射情況如下:
緩存哈希容錯(cuò)性示意圖 如圖所見,在這個(gè)例子中,僅僅刪除了一個(gè)服務(wù)節(jié)點(diǎn),也導(dǎo)致了哈希值的大面積更新,哈希值的更新也是意味著節(jié)點(diǎn)緩存數(shù)據(jù)的遷移(緩存數(shù)據(jù)表示心好累)。 一致性哈希算法負(fù)載均衡正是由于普通哈希算法實(shí)現(xiàn)的緩存負(fù)載均衡存在擴(kuò)展能力和容錯(cuò)能力差問題,所以我們引入一致性哈希算法,那么什么是一致性哈希呢?先來看下wiki上對一致性Hash的定義
這篇描述一致性哈希的論文發(fā)表于1997年,閱讀無障礙的同學(xué)可以直接看看大佬的論文理解更深刻,附上論文下載鏈接:http://citeseerx.ist./viewdoc/summary?doi=10.1.1.147.1879 ![]() 一致性hash論文 一句話概括一致性哈希:就是普通取模哈希算法的改良版,哈希函數(shù)計(jì)算方法不變,只不過是通過構(gòu)建環(huán)狀的 Hash 空間代替普通的線性 Hash 空間。具體做法如下: 首先,選擇一個(gè)足夠大的Hash空間(一般是 0 ~ 2^32)構(gòu)成一個(gè)哈希環(huán)。 ![]() 一致性哈希環(huán) 然后,對于緩存集群內(nèi)的每個(gè)存儲服務(wù)器節(jié)點(diǎn)計(jì)算 Hash 值,可以用服務(wù)器的 IP 或 主機(jī)名計(jì)算得到哈希值,計(jì)算得到的哈希值就是服務(wù)節(jié)點(diǎn)在 Hash 環(huán)上的位置。 ![]() 節(jié)點(diǎn)哈希 最后,對每個(gè)需要存儲的數(shù)據(jù) key 同樣也計(jì)算一次哈希值,計(jì)算之后的哈希也映射到環(huán)上,數(shù)據(jù)存儲的位置是沿順時(shí)針的方向找到的環(huán)上的第一個(gè)節(jié)點(diǎn)。下圖舉例展示了節(jié)點(diǎn)存儲的數(shù)據(jù)情況,我們下面的說明也是基于目前的存儲情況來展開。 ![]() image 原理講完了,來看看為什么這樣的設(shè)計(jì)能解決上面普通哈希的兩個(gè)問題。 擴(kuò)展能力提升前面我們分析過,普通哈希算法當(dāng)需要擴(kuò)容增加服務(wù)節(jié)點(diǎn)的時(shí)候,會導(dǎo)致原油哈希映射大面積失效?,F(xiàn)在,我們來看下一致性哈希是如何解決這個(gè)問題的。 如下圖所示,當(dāng)緩存服務(wù)集群要新增一個(gè)節(jié)點(diǎn)node3時(shí),受影響的只有 key3 對應(yīng)的數(shù)據(jù) value3,此時(shí)只需把 value3 由原來的節(jié)點(diǎn) node0 遷移到新增節(jié)點(diǎn) node3 即可,其余節(jié)點(diǎn)存儲的數(shù)據(jù)保持不動。 一致性哈希-擴(kuò)展節(jié)點(diǎn) 容錯(cuò)能力提升普通哈希算法當(dāng)某一服務(wù)節(jié)點(diǎn)宕機(jī)下線,也會導(dǎo)致原來哈希映射的大面積失效,失效的映射觸發(fā)數(shù)據(jù)遷移影響緩存服務(wù)性能,容錯(cuò)能力不足。一起來看下一致性哈希是如何提升容錯(cuò)能力的。 如下圖所示,假設(shè) node2 節(jié)點(diǎn)宕機(jī)下線,則原來存儲于 node2 的數(shù)據(jù) value2 和 value5 ,只需按順時(shí)針方向選擇新的存儲節(jié)點(diǎn) node0 存放即可,不會對其他節(jié)點(diǎn)數(shù)據(jù)產(chǎn)生影響。一致性哈希能把節(jié)點(diǎn)宕機(jī)造成的影響控制在順時(shí)針相鄰節(jié)點(diǎn)之間,避免對整個(gè)集群造成影響。 ![]() 一致性哈希-刪除節(jié)點(diǎn) ![]() 一致性哈希優(yōu)化存在的問題上面展示了一致性哈希如何解決普通哈希的擴(kuò)展和容錯(cuò)問題,原理比較簡單,在理想情況下可以良好運(yùn)行,但在實(shí)際使用中還有一些實(shí)際問題需要考慮,下面具體分析。 數(shù)據(jù)傾斜試想一下若緩存集群內(nèi)的服務(wù)節(jié)點(diǎn)比較少,就像我們例子中的三個(gè)節(jié)點(diǎn),而哈希環(huán)的空間又有很大(一般是 0 ~ 2^32),這會導(dǎo)致什么問題呢? 可能的一種情況是,較少的服務(wù)節(jié)點(diǎn)哈希值聚集在一起,比如下圖所示這種情況 node0 、node1、node2 聚集在一起,緩存數(shù)據(jù)的 key 哈希都映射到 node2 的順時(shí)針方向,數(shù)據(jù)按順時(shí)針尋找存儲節(jié)點(diǎn)就導(dǎo)致全都存儲到 node0 上去,給單個(gè)節(jié)點(diǎn)很大的壓力!這種情況稱為數(shù)據(jù)傾斜。 ![]() 一致性哈希-數(shù)據(jù)傾斜 節(jié)點(diǎn)雪崩數(shù)據(jù)傾斜和節(jié)點(diǎn)宕機(jī)都可能會導(dǎo)致緩存雪崩。 拿前面數(shù)據(jù)傾斜的示例來說,數(shù)據(jù)傾斜導(dǎo)致所有緩存數(shù)據(jù)都打到 node0 上面,有可能會導(dǎo)致 node0 不堪重負(fù)被壓垮了,node0 宕機(jī),數(shù)據(jù)又都打到 node1 上面把 node1 也打垮了,node1 也被打趴傳遞給 node2,這時(shí)候故障就像像雪崩時(shí)滾雪球一樣越滾越大。 還有一種情況是節(jié)點(diǎn)由于各種原因宕機(jī)下線。比如下圖所示的節(jié)點(diǎn) node2 下線導(dǎo)致原本在node2 的數(shù)據(jù)壓到 node0 , 在數(shù)據(jù)量特別大的情況下也可能導(dǎo)致節(jié)點(diǎn)雪崩,具體過程就像剛才的分析一樣。 總之,連鎖反應(yīng)導(dǎo)致的整個(gè)緩存集群不可用,就稱為節(jié)點(diǎn)雪崩。 ![]() 一致性哈希-節(jié)點(diǎn)雪崩 虛擬節(jié)點(diǎn)那該如何解決上述兩個(gè)棘手的問題呢?可以通過「虛擬節(jié)點(diǎn)」的方式解決。 所謂虛擬節(jié)點(diǎn),就是對原來單一的物理節(jié)點(diǎn)在哈希環(huán)上虛擬出幾個(gè)它的分身節(jié)點(diǎn),這些分身節(jié)點(diǎn)稱為「虛擬節(jié)點(diǎn)」。打到分身節(jié)點(diǎn)上的數(shù)據(jù)實(shí)際上也是映射到分身對應(yīng)的物理節(jié)點(diǎn)上,這樣一個(gè)物理節(jié)點(diǎn)可以通過虛擬節(jié)點(diǎn)的方式均勻分散在哈希環(huán)的各個(gè)部分,解決了數(shù)據(jù)傾斜問題。 由于虛擬節(jié)點(diǎn)分散在哈希環(huán)各個(gè)部分,當(dāng)某個(gè)節(jié)點(diǎn)宕機(jī)下線,他所存儲的數(shù)據(jù)會被均勻分配給其他各個(gè)節(jié)點(diǎn),避免對單一節(jié)點(diǎn)突發(fā)壓力導(dǎo)致的節(jié)點(diǎn)雪崩問題。 下圖展示了虛擬節(jié)點(diǎn)的哈希環(huán)分布,其中左邊是沒做虛擬節(jié)點(diǎn)情況下的節(jié)點(diǎn)分布,右邊背景色綠色兩個(gè)的 node0 節(jié)點(diǎn)是 node0 節(jié)點(diǎn)的虛擬節(jié)點(diǎn);背景色紅色的 node1 節(jié)點(diǎn)是 node1 的虛擬節(jié)點(diǎn)。 ![]() 一致性哈希-虛擬節(jié)點(diǎn) ![]() 總結(jié)一下本文首先介紹了什么是哈希算法和常見的哈希算法,以及常見散列方式,接著說明基于普通哈希算法的緩存負(fù)載均衡實(shí)現(xiàn),并舉例說明普通算法的擴(kuò)展性和容錯(cuò)性方便存在的問題。 為了解決普通算法的擴(kuò)展性和容錯(cuò)性問題引入一致性哈希算法,圖解和舉例分析了一致性哈希是如何提高擴(kuò)展性和容錯(cuò)性。最后粗糙的一致性哈希算法也存在數(shù)據(jù)傾斜和節(jié)點(diǎn)雪崩的問題,講解了如何利用虛擬節(jié)點(diǎn)優(yōu)化一致性哈希算法,解決數(shù)據(jù)傾斜和雪崩問題。至此,一致性哈希你學(xué)會了嗎? 一致性哈希這個(gè)知識點(diǎn)不難,但是經(jīng)常會考察到,就像布隆過濾器算法一樣,沒聽過的人覺得很高端,研究一下也就那么一回事,所以知識面要寬才能吊打面試官啊同學(xué)們! 感謝各位的閱讀,文章的目的是分享對知識的理解,技術(shù)類文章我都會反復(fù)求證以求最大程度保證準(zhǔn)確性,若文中出現(xiàn)明顯紕漏也歡迎指出,我們一起在探討中學(xué)習(xí)。 如果覺得文章寫的還行,對你有所幫助,不要白票 lemon,動動手指點(diǎn)個(gè)「在看」或「分享」是對我持續(xù)創(chuàng)作的最大支持。 今天的技術(shù)分享就到這里,我們下期再見。 ![]() |
|