白話分佈式系統中的一致性哈希算法

本文首發於:白話分佈式系統中的一致性哈希算法
微信公衆號:後端技術指南針
持續輸出乾貨 歡迎關注!前端

經過本文將瞭解到如下內容:node

  1. 分佈式系統的概念和做用
  2. 分佈式系統經常使用負責均衡策略
  3. 普通哈希取模策略優缺點
  4. 一致性哈希算法的定義和思想
  5. 一致性哈希的基本過程
  6. Redis集羣中一致性哈希的實現

1.分佈式系統的基本概念算法

  • 分佈式系統與高併發高可用

當今高併發和海量數據處理等場景愈來愈多,實現服務應用的高可用、易擴展、短延時等成爲必然。後端

在此狀況下分佈式系統應運而生,互聯網的場景無外乎存儲和計算,所以分佈式系統能夠簡單地分爲:緩存

  1. 分佈式存儲
  2. 分佈式計算

所謂分佈式系統就是一批計算機組合起來共同對外提供服務,對於用戶來講具體有多少規模的計算機完成了此次請求,徹底是無感知的。服務器

分佈式系統中的計算機越多,意味着計算和存儲資源等也就越多,可以處理的併發訪問量也就越大,響應速度也越快。微信

如圖爲簡單總體架構圖:網絡

  1. 大前端 主要實現了服務應用對應的全部流量的接入,好比xyz域名下可能有N個子服務,這一層涉及不少網絡流量的處理,也頗有挑戰,像百度的BFE(百度統一前端)接入了百度的大部分流量,每日轉發1萬億次,峯值QPS1000w。
  2. 中間層 完成了各個服務的調度和分發,粒度相比大前端接入層更細緻一些,這一層實現了用戶的無感知體驗,能夠簡單理解爲反向代理層。
  3. 業務層 完成了數據存儲、數據計算、數據緩存等,各個業務環節高度解耦,而且基於集羣化來實現。
  • 集羣和分佈式的區別與聯繫

集羣是從原來的單機演變來的,單臺機器扛不住就加機器,直到服務負載、穩定性、延時等指標都知足,架構

集羣中的N臺機器上部署同樣的程序,就像一臺機器被複制多份同樣,這種形式就是集羣化。併發

分佈式是將一個完整的系統,按照業務功能拆分紅一個個獨立的子系統,這些服務之間使用更高效的通訊協議好比RPC來完成調度,

各個子服務就像在一臺機器上同樣,實現了業務解耦,同時提升了併發能力確實不賴。

一個大的分佈式系統能夠理解拆分以後的子服務使用集羣化,一個個子服務之間使用相似於RPC的協議串聯,組成一個龐大的存儲和計算網絡。

如圖爲簡單的分佈式系統結構:

注:圖片來自網絡 詳見參考1

2.分佈式系統的分發

  • 經常使用負載均衡策略

以分佈式系統中的存儲和緩存爲例,若是存儲集羣中有5臺機器,若是這時有請求,就須要考慮從哪臺機器獲取數據,通常有幾種方法:

  1. 隨機訪問
  2. 輪詢策略
  3. 權重輪詢策略
  4. Hash取模策略
  5. 一致性哈希策略

各類方法都有各自的優缺點:

隨機訪問可能形成服務器負載壓力不均衡;輪詢策略請求均勻分配,但當服務器有性能差別,沒法按性能分發;

權值須要靜態配置,沒法自動調節;哈希取模若是機器動態變化會致使路由產生變化,數據產生大量遷移。

  • Hash取模策略

Hash取模策略是其中經常使用的一種作法,它能夠保證相同請求相同機器處理,這是一種並行轉串行的方法,工程中很是常見。

筆者在剛畢業作流量分析時就是按照報文的五元組信息作key來決定服務內的業務線程id,這樣確保相同的TCP/HTTP鏈接被相同的線程處理,

避免了線程間的通訊和同步,實現了無鎖化處理,因此仍是頗有用的。

1 index = hash_fun(key) % N

從上面的普通hash取模公式能夠看到,若是N不變或者能夠本身主動控制,就能夠實現數據的負載均衡和無鎖化處理,可是一旦N的變化不被控制,那麼就會出現問題。

  • Hash取模的弊端

階段一:

目前有N=4臺機器S1-S4,請求拼接key經過hash函數%N,獲取指定的機器序號,並將請求轉發至該機器。

階段二:

S3機器由於磁盤故障而宕機,這時代理層得到故障報警調整N=3,這時就出現了問題,若是做爲緩存使用,

S3機器上的全部key都將出現CacheMiss,原來存放在S1的key=abc使用新的N,被調整到S4,

這樣abc也出現CacheMiss,由於在S4上找不到abc的數據,這種場景就是牽一髮而動全身,在緩存場景中會形成緩存擊穿,若是量很大會形成緩存雪崩。

階段三:

因爲S3宕機了,所以管理員增長了一臺機器S5,代理層再次調整N=4,

此時又將出現相似於階段二的數據遷移,仍然會出現CacheMiss的狀況。

3.一致性哈希算法

  • 一致性哈希的定義

In computer science, consistent hashing is a special kind of hashing such that when a hash table is resized,

only K/n keys need to be remapped on average,

where K is the number of keys, and n is the number of slots.

維基百科-Consistent hashing

翻譯一下:

一致哈希 是一種特殊的哈希算法。
在使用一致哈希算法後,哈希表槽位數(大小)的改變平均只須要對K/n 個關鍵字從新映射,其中 K是關鍵字的數量,n是槽位數量。
在傳統的哈希表中,添加或刪除一個槽位的幾乎須要對全部關鍵字進行從新映射。維基百科-一致性哈希

從定義能夠知道,一致性哈希是一種特殊的哈希算法,區別於哈希取模,這種特殊的哈希算法實現了少許數據的遷移,

避免了幾乎所有數據的移動,這樣就解決了普通hash取模的動態調整帶來的全量數據變更。

  • 解決思路是什麼

先不看算法的具體實現,先想一想普通hash取模的問題根源是什麼?

  • N的變更

沒錯!根源就在於N的變更,那麼若是N被固定住呢?而且讓N很大,那麼每次被移動的key數就是K_all/Slot_n,也就是有槽位的概念,或者說是小分片的概念,

直白一點就是雞蛋放到了不少不少的固定數量的籃子裏:

1 Key_all 存儲的所有key的數量
2 Slot_n 總的槽位或者分片數
3 Min_Change 爲最小移動數量 
4 Min_change = Key_all/Slot_n
5 Min_change 也是數據的最小分片Shard
  • 小分片的歸屬

這裏還有一個問題要解決,將N固定且設置很大以後,數據分片shard變得很是小了,這時就有shard的所屬問題,

也就是好比N=100w,此時集羣有10臺,那麼每臺機器上理論上平均有10w,固然能夠根據機器的性能來作差別化的歸屬配置,

性能強的多分一些shard,性能差的少分一些shard。

問題到這裏,基本上已經守得雲開見月明瞭,不過仍是來看看1997年麻省理工發明的一致性哈希算法原理吧。

  • Karger的一致性哈希算法

一致性哈希算法在1997年由麻省理工學院的Karger等人在解決分佈式Cache中提出的,設計目標是爲了解決因特網中的熱點(Hot spot)問題,初衷和CARP十分相似。一致性哈希修正了CARP使用的簡單哈希算法帶來的問題,使得DHT能夠在P2P環境中真正獲得應用。一致性哈希算法

  • 數據在哈希環上的分片

正如咱們前面的思考,Karger的一致性哈希算法將N設置爲2^32,造成了一個0~(2^32-1)的哈希環,也就是至關於普通Hash取模時N=2^32。

注:圖片來自網絡 詳見參考2在將數據key進行hash計算時就落在了0~(2^32-1的哈希環上,

若是總的key數量爲Sum,那麼單個哈希環的最小單位上的key數就是:

1 Unit_keys = Sum/2^32

因爲N很是大因此哈希環最小單位的數據量unit_keys小了不少。

  • 服務器節點和哈希環分片的分配

注:圖片來自網絡 詳見參考2

將服務器結點也做爲一種key分發到哈希環上:

1 con_hash(ip_key)%2^32

一致性哈希算法使用順時針方法實現結點對哈希環shard的歸屬,可是因爲服務器結點的數量相比2^32會少很是多,

所以很稀疏,就像宇宙空間中的天體,你覺得天體不少,可是相比浩渺的宇宙仍是空空如也。

實體服務器結點少許相比哈希環分片數據不多,這種特性決定了一致性哈希的數據傾斜,

因爲數量少致使服務節點分佈不均,形成機器負載失衡,如圖所示,服務器1的負載遠大於其餘機器:

注:圖片來自網絡 詳見參考2

虛擬節點的引入:

這個說白了服務器結點不夠,就讓服務器的磁盤、內存、CPU全去佔位置,

現實生活中也這樣:12306出來以前,火車站連夜排隊買票,這時什麼書包、水杯、眼鏡都表明了張3、李4、王二麻子。

一樣的道理,將服務器結點根據某種規則來虛擬出更多結點,可是這些虛擬節點就至關於服務器的分身。

好比採用以下規則在ip後綴增長#index,來實現虛擬節點的定位:

1 vnode_A_index = con_hash(ip_key_#A)%2^32
2 vnode_B_index = con_hash(ip_key_#B)%2^32
3 ...
4 vnode_k_index = con_hash(ip_key_#k)%2^32

注:圖片來自網絡 詳見參考2

這是因爲引入了虛擬節點,所以虛擬節點的分片都要實際歸屬到真實的服務節點上,所以在實際中涉及到虛擬節點和實體結點的映射問題。

  • 新增服務器結點

注:圖片來自網絡 詳見參考2

當管理員新增了服務器4時,原來在服務器3和服務器1之間分佈的哈希環單元上的數據,將有一部分遷移到服務器4,

固然因爲虛擬節點的引入,這部分數據遷移不會很大,並非服務器4和服務器1之間的數據都要順時針遷移,所以這樣就實現了增長機器時,只移動少許數據便可。

  • 刪除服務器結點

注:圖片來自網絡 詳見參考2

當服務器結點2發生宕機,此時須要被摘除進行故障轉移,原來S2以及其虛擬節點上的數據都將進行順時針遷移到下一個實體結點或者虛擬結點。

4.Redis的一致性哈希實現

Redis cluster 擁有固定的16384個slot,slot是虛擬的且被分佈到各個master中,當key 映射到某個master 負責slot時,就由對應的master爲key 提供服務。

每一個Master節點都維護着一個位序列bitmap爲16384/8字節,也就是Master使用bitmap的原理來表徵slot的下標,

Master 節點經過 bit 來標識哪些槽本身是否擁有,好比對於編號爲1的槽,Master只要判斷序列的第二位是否是爲1便可。

這樣就創建了分片和服務結點的所屬關係,因此整個過程也是兩個原則,符合上文的一致性哈希的思想。

1 hash_slot_index =CRC16(key) mod 16384

注:圖片來自網絡 詳見參考4

5.總結和思考

  • 一致性哈希算法的兩個關鍵點

一致性哈希算法是一種特殊的哈希算法,特殊之處在於將普通哈希取模的N進行固定,從而確保了相同的key必然是相同的位置,從而避免了牽一髮而動全身的問題,這是理解一致性哈希的關鍵。

一致性哈希算法的另一個要點就是將N固定且設置很大以後,實際上就是進行數據分片Sharding,分佈的小片就要和實際的機器產生關聯關係,也就是哪臺機器負責哪些小分片。

可是一致性哈希算法並非從完全解決了因爲動態調整服務器數據產生的數據遷移問題,

而是將原來普通哈希取模形成的幾乎所有遷移,下降爲小部分數據的移動,是一種很是大的優化,在工程上基本上能夠知足要求。

一致性哈希算法的關鍵有兩點:

  1. 大量固定數量的小數據塊的分片
  2. 小分片的服務器歸屬問題
  • 一致性哈希算法的其餘工程版本

像Redis並無使用2^32這種哈希環,而是採用了16384個固定slot來實現的,而後每一個服務器Master使用bitmap來肯定本身的管轄slot,

管理員能夠根據機器的配置和負載狀況進行slot的動態調整,基本上解決了最開始的幾種負載均衡策略的不足。

因此假如讓你設計一個一致性哈希算法,只要把握兩個原則便可,並非只有麻省理工Karger的一種哈希算法,它只是提供了一種思想和方向。

  • 天馬行空

一直有個疑問問什麼要用"一致性哈希算法" 這個名字,讓我總和分佈式系統中的一致性協議(eg最終一致性)混淆。

英文原文是Consistent hashing,其中Consistent譯爲"一致的,連貫的",我以爲連貫的更貼切一些,

覺得這種特殊的哈希算法實現了普通哈希取模算法的平滑連貫版本,稱爲連貫性哈希算法,好像更合適,一點愚見,水平有限,看看就完事了。

5.參考資料

6.推薦閱讀

白話布隆過濾器BloomFilter
理解緩存系統的三個問題
幾種高性能網絡模型
二叉樹及其四大遍歷
理解Redis單線程運行模式
Linux中各類鎖及其基本原理
理解Redis持久化
深刻理解IO複用之epoll
深刻理解跳躍鏈表[一]
理解堆和堆排序
理解堆和優先隊列

7.關於本公衆號

開號不久做者力爭持續輸出原創乾貨,若是文章有幫助到你,

但願朋友們多多轉發和分享,做者會更加有動力推出更好的文章,共同進步。

微信公衆號:後端技術指南針

相關文章
相關標籤/搜索