一致性哈希和分佈式哈希表

一致性哈希(consistent hashing)和分佈式哈希表(DHT: Distributed Hash Table)在最近的學習中經常用到,但是兩個概念經常糾纏在一起,不容易分清楚。有時候就不明白這裏爲什麼說的是consistent hashing,而不是用DHT。

從字面的意思來區分:consistent hashing 是一種滿足特殊需求的哈希;DHT 是通過哈希實現的分佈式的表,歸根到底是一個分佈式系統。consistent hashing 是理論上節點變化最少數據遷移的哈希方法,而DHT 在實現上更加具體,DHT 把傳統的單個K-V 表在分佈式多個節點中進行劃分,既可以採用consistent hashing 實現,也可以採用其他哈希方法。

一致性哈希   consistent hashing


    DHT的一種實現。本質還是一個哈希算法。回想平時我們做負載均衡,按querystring簽名對後端節點取模是最簡單也是最常用的算法,但節點的增刪所造成的問題顯而易見:原有的請求幾乎都落不到同一臺機器上。優化一點的是carp算法,只讓1/n的數據受到影響。
一致性哈希,似乎最早提出是在分佈式緩存裏面的,讓節點震盪的時候,影響最小。不過現在已經應用在分佈式存儲和p2p系統裏面。




在傳統的哈希表中,當劃分哈希區域的分組數量發生變化時,幾乎所有的keys 都需要重新排列到不同的分組。但如果使用一致性哈希,平均只需要k/n 個keys 重排,k 是keys 的個數,n 是分組的個數。基於這點性質,可以在DHT 中使用consistent hashing 。

hash(o) mod n 是傳統的哈希用於確定對象分組位置方法,o 是對象,n 是分組個數,對應分佈式系統節點個數,計算的結果即是存儲該對象的節點編號。當節點個數n 發生變化時,而對象的哈希結果不變,這樣幾乎所有的對象位置都需要重排,造成分佈式系統網絡流量的嚴重顛簸(churn)。而一致性哈希很好的解決了這個問題。

consistent hashing 使用一個固定的模數R 對對象的哈希結果取模hash(o) mod R。R 可以表示哈希結果的範圍,也可以表示對應哈希環(Ring)的範圍,在chord 中 $R=2^{32}$ 。並使用同樣哈希函數和模數R 對所有的節點進行同樣的計算hash(node.id) mod R。這樣就把節點和對象都哈希到同一個大小爲R 的環(Ring)上了。每個節點管理、存儲它和它環上前驅節點之間範圍的對象,當有新的節點加入或者退出時,遵循同樣規則。這樣的好處是剝離了對象分佈和節點的關係,節點個數發生變化時對象在環上的分佈不發生變化,最大限度減少了對象在節點上的遷移。

下面圖示了consistent hashing 節點加入、退出過程(圖片來源):

consistent-hashing-1

在只有兩個節點node 1 和node 2 ,哈希結果爲Key1 和key2 ,每個節點存儲前驅節點(如key2>key1)和自己之間範圍的對象(values)。在節點個數比較少的時候就可能存在負載不均衡的情況,這個可用通過引入虛擬節點來解決。

consistent-hashing-2

添加一個節點node3 (key3)的情形。根據每個節點管理、存儲它和它環上前驅節點之間範圍的對象的原則,node3(key3)分擔了key2 的空間。

consistent-hashing-3

刪除一個節點node1(key1)的情形。key1 的空間被node3(key3)接管了。

consistent-hashing-4

引入虛擬節點後的結果。爲了避免因爲節點空間不均衡導致負載不平衡,每個節點引入等量的虛擬節點分佈在環上,虛擬節點使用同樣的方法劃分空間。採用v1_key1=hash(node1.id.v1) ……vn_key1=hash(node1.id.vn)  的方法可以得到節點node1 的n 個虛擬節點vi_key1。

consistent hashing 具有以下性質:

  1. Spread: 發散性指的是對象分發到各個節點上,每個節點只需要存儲所有對象的一部分
  2. Load: 負載指的是任何一個節點都不會保存過多的對象數據
  3. Smothness: 平滑性指的是節點改變帶來的對象遷移是平緩的。不會因爲節點的加入、退出導致大量數據遷移
  4. Balance: 平衡性指的是每個對象隨機分配到節點
  5. Monotonic: 單調性指的是當一個節點加入時,只有分配到該節點的對象發生了重排

 

分佈式哈希表   Distributed Hash Table

                兩個key point:每個節點只維護一部分路由;每個節點只存儲一部分數據。從而實現整個網絡中的尋址和存儲。
DHT只是一個概念,提出了這樣一種網絡模型。並且說明它是對分佈式存儲很有好處的。但具體怎麼實現,並不是DHT的範疇。

分佈式哈希表DHT 是一種去中心化的分佈式系統,提供一種類似於哈希表的查詢功能。

DHT 的研究最初是因爲P2P 系統的興起,如Napster,Gnutella 和Freenet。

Napster 是最早的P2P 服務的代表作,有一箇中心index server 保存資源的index(key),當有節點加入、退出和查詢時,與index server 進行交互,獲取資源信息,存在單點故障,在遇到法律糾紛時,這個index server 很容就被關閉了。

Gnutella 去中心化得鬆散佈局,當節點發起查詢時,通過洪泛(flooding)的方式查詢:每個收到查詢命令的節點向其所有其他鄰居節點發送類似請求。並通過TTL 控制洪泛的區域。

Freenet 首先採用了DHT,每個對象(文件)都關聯一個key,具有相似的key 的文件保存在相似的節點上,並通過一個基於key 的啓發式的路由進行查詢。

DHT 可以認爲是由keyspace(key 空間)、keyspace partition(key 空間劃分)、overlay network(覆蓋網)組成。keyspace 很好理解,是所有定長字符串的空間。keyspace partition 方案將keyspace 上不同區域劃分到不同的參與節點上。overlay network(覆蓋網)將節點連接起來,使得網絡中節點通過這個網絡可以找到保存指定key 的節點。consistent hashing 對比DHT 更多的是提供了一種keyspace 的劃分方法,各種consistent hashing 的也用在了DHT 中進行空間劃分。爲了更好的理解overlay network 的組成,有這麼一段話:

Each node maintains a set of links to other nodes, these links forms the overlay network.

【譯】每個節點維持到其他節點的鏈接,這些鏈接形成了覆蓋網。

可以看出overlay network 已經從物理層的網絡抽象成邏輯層上鍊接各個節點、各個節點保存的路由信息以及路由協議的集合。在overlay network 有兩個重要參數:鄰居節點數/度數(Degree)和跳數/路由長度(Route length)。度數衡量每個節點保存鄰居節點信息的多少,度數越大每個節點鄰居越多,保存和維持的信息就更多,需要的路由長度就越短。跳數指的是從該節點到保存特定key 的對象的節點的平均路由長度。他們的關係有下表:

度數

路由長度

備註

O(1) O(n)  
O(log n) O(log n/log(log n))  
O(log n) O(log n) 最常見(比如 chord),但不是最優的解
O(1) O(log n)  
O($\sqrt{n}$) O(1)  

DHT 具有三點性質:

  1. Decentralization: 去中心化,節點之間相互配合形成整個系統,而不需要一種中心節點協調
  2. Fault tolerance: 容錯,即使不斷有節點加入、退出或者失效,也要能夠保證系統穩定
  3. Scalability: 擴展性,系統在成千上萬個節點時也能夠有效地運行

Chord算法:
一致性哈希有多種實現算法,最關鍵的問題在於如何定義數據分割策略和節點快速查詢。

chord算是最爲經典的實現。cassandra中的DHT,基本是chord的簡化版。

網絡中每個節點分配一個唯一id,可以通過機器的mac地址做sha1,是網絡發現的基礎。

假設整個網絡有N 個節點,並且網絡是呈環狀。兩個節點間的距離定義爲每個節點會存儲一張路由表(finger表),表內順時針按照離本節點2、4、8、16、32.……2i的距離選定log2N個其他節點的ip信息來記錄。

存儲方面:數據被按一定規則切割,每一份數據也有一個獨立id(查詢key),並且和節點id的值域是一樣的。然後查找節點,如果存在和數據id一樣的節點id,則將這份數據存在該節點上;如果不存在,則存儲到離該數據id距離最近的節點上。同時,爲了保證數據的可靠性,會順時針往下找K個冗餘節點,存儲這份數據。一般認爲K=3是必須的。

查詢方面:先從自己的路由表中,找一個和數據id距離最近、並且存活在網絡中的節點next。如果該節點的id巧合和數據id相等,那麼恭喜你。如果不相等,則到next進行遞歸查找。一般或需要經過多次查詢才能找到數據所在的節點,而這個次數是可以被證明小於等於log2N的。

在這個查詢的過程中就體現了路由表的選取優勢了,其實是實現了一個二分查找,從每個節點來觀察網絡,都是將網絡分成了log2N塊,最大一塊裏面有N/2個節點。路由表裏面其實是記錄了每一塊的第一個節點。這樣每一次查詢,最少排除了一半的節點。保證在log2N次內找到目標節點。

新增一個節點i,需要預先知道網絡中已經存活的一個節點j,然後通過和節點j交互,更新自己和其他節點的路由表。並且,需要將離自己距離最近的節點中的數據copy過來,以提供數據服務。

損失一個節點,路由算法會自動跳過這個節點,並且依靠數據的冗餘來持續提供服務。

KAD算法(Kademlia)
個人覺得,kad算法其實是在chord上做的優化。主要是兩個點:
1、用二進制(32/64/128)表示一個節點的id,兩節點的id異或運算得到節點間的距離。
2、 每個節點保持的路由信息更豐富,同樣是將整個網絡按照劃分成log2N份,在chord中,是保持log2N個路由節點,但在kad裏面,是保存了 log2N個隊列。每個隊列長度爲配置值K,記錄網絡中對應節點區域的多個節點,並且根據活躍時間對這些節點進行換入換出。
第一點是方便進行網絡劃分,節點按照二進制中每一bit的0或1建成一棵二叉樹。

第二點是使得節點查詢更迅速。從分割情況我們就可以得知,最壞情況不會差於chord,但保存更多的節點使得命中概率更高。另外隊列中根據活躍時間進行換入換出,更有利於在p2p這種節點變更頻繁的網絡中快速找到有效的節點。

關於kad的介紹,這篇文章講的比較詳細wenku.baidu.com/view/ee91580216fc700abb68fcae.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章