1. 程式人生 > >淺析分散式系統中的一致性雜湊演算法

淺析分散式系統中的一致性雜湊演算法

通過本文將瞭解到以下內容:

  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. 一致性雜湊策略

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

  1. 隨機訪問可能造成伺服器負載壓力不均衡;輪詢策略請求均勻分配,但當伺服器有效能差異,無法按效能分發;
  2. 權值需要靜態配置,無法自動調節;雜湊取模如果機器動態變化會導致路由產生變化,資料產生大量遷移。
  • 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.

翻譯一下:

一致雜湊 是一種特殊的雜湊演算法。
在使用一致雜湊演算法後,雜湊表槽位數(大小)的改變平均只需要對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出來之前,火車站連夜排隊買票,這時什麼書包、水杯、眼鏡都代表了張三、李四、王二麻子。

同樣的道理,將伺服器結點根據某種規則來虛擬出更多結點,但是這些虛擬節點就相當於伺服器的分身。

比如採用如下規則在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譯為"一致的,連貫的",我覺得連貫的更貼切一些,

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

 

6.參考資料

  1. https://waylau.com/talk-about-distributed-system/
  2. https://juejin.im/post/5ce4a666e51d4558936a9fdd
  3. https://www.oschina.net/news/111425/bfe-opensouced
  4. https://www.jianshu.com/p/b398250d661a