分布式散列表(DHT)及具体实现Kademlia(kad)/Chord
分布式散列列表也稱為分布式哈希表,英文distributed hash table,簡稱 DHT。
分布式散列列表在概念上類似與傳統(tǒng)的散列列表,差異在于傳統(tǒng)的散列列表主要是?用于單機(jī)。
分布式散列列表主要是?用于分布式系統(tǒng)(此時(shí),分布式系統(tǒng)的節(jié)點(diǎn)可以通俗理理解為散列列表中的 bucket),分布式散列列表主要是?用來存儲?大量量的(甚?至是海?量量的)數(shù)據(jù)。在實(shí)際使?用場景中,直接對所存儲的“每?一個(gè)業(yè)務(wù)數(shù)據(jù)”計(jì)算散列列值,然后用散列列值作為 key,業(yè)務(wù)數(shù)據(jù)本身是 value。
分布式散列列表(DHT)的難點(diǎn):
1、無中心導(dǎo)致的難點(diǎn)
DHT的誕生,是為了解決之前P2P技術(shù)的缺陷。其中一個(gè)缺陷是中央服務(wù)器導(dǎo)致的單點(diǎn)故障。
因此 DHT 就不能再依靠中央服務(wù)器。而沒有了中央服務(wù)器,就需要提供一系列機(jī)制來實(shí)現(xiàn)節(jié)點(diǎn)之間的通訊。
2、海量數(shù)據(jù)導(dǎo)致的難點(diǎn)
? ? ? ?DHT的很多使用場景是為了承載海量數(shù)據(jù)(PB 或更高級別)。
由于數(shù)據(jù)是海量的,每個(gè)節(jié)點(diǎn)只能存儲(整個(gè)系統(tǒng)的)一小部分?jǐn)?shù)據(jù)。需要把數(shù)據(jù)均勻分?jǐn)偟矫總€(gè)節(jié)點(diǎn)。
3、節(jié)點(diǎn)動態(tài)變化導(dǎo)致的難點(diǎn)
很多DHT的使用場景是在公網(wǎng)(互聯(lián)網(wǎng))上,參與DHT的節(jié)點(diǎn)(主機(jī))會出現(xiàn)頻繁變化,每時(shí)每刻都有新的節(jié)點(diǎn)上線,也
會有舊的節(jié)點(diǎn)下線。在這種情況下,需要確保數(shù)據(jù)依然是均勻分?jǐn)偟剿泄?jié)點(diǎn)。
(特別強(qiáng)調(diào)一下:傳統(tǒng)的散列表在這種情況下的困難)
因?yàn)閭鹘y(tǒng)散列表在針對 key 計(jì)算出散列值之后,需要用散列值和桶數(shù)進(jìn)行某種運(yùn)算(比如:取模運(yùn)算),從而得到桶的編號。
如果桶的數(shù)量出現(xiàn)變化,就會影響到上述取模運(yùn)算的結(jié)果,然后導(dǎo)致數(shù)據(jù)錯(cuò)亂。
4、高效查詢導(dǎo)致的難點(diǎn)
對于節(jié)點(diǎn)數(shù)很多的分布式系統(tǒng),如何快速定位節(jié)點(diǎn),同時(shí)又不消耗太多網(wǎng)絡(luò)資源,這也是一個(gè)挑戰(zhàn)。
? ? ? ?DHT 必須有更高效的查找機(jī)制。而且這種查找機(jī)制要能適應(yīng)節(jié)點(diǎn)動態(tài)變化這個(gè)特點(diǎn)。
分布式散列表(DHT)解決上述難點(diǎn)
DHT 采用如下一些機(jī)制來解決上述問題,并滿足分布式系統(tǒng)比較苛刻的需求。
1、散列算法的選擇
前面提到: DHT 通常是直接拿業(yè)務(wù)數(shù)據(jù)的散列值作為 key,業(yè)務(wù)數(shù)據(jù)本身作為 value。
考慮到 DHT 需要承載的數(shù)據(jù)量通常比較大,散列函數(shù)產(chǎn)生的散列值范圍(keyspace)要足夠大,以防止太多的碰撞。更進(jìn)一步,如果 keyspace大到一定程度,使得隨機(jī)碰撞的概率小到忽略不計(jì),就有助于簡化 DHT 的系統(tǒng)設(shè)計(jì)。
通常的 DHT 都會采用大于等于128比特的散列值。
2、同構(gòu)的node ID與data key
? ?DHT屬于分布式系統(tǒng)的一種。既然是分布式系統(tǒng),意味著存在多個(gè)節(jié)點(diǎn)。在設(shè)計(jì)分布式系統(tǒng)的時(shí)候,一種常見的做法是:給每一個(gè)節(jié)點(diǎn)(node)分配唯一的ID。
很多 DHT 的設(shè)計(jì)會讓node ID采用跟data key同構(gòu)的散列值。這么搞的好處是:
? ? 2.1、當(dāng)散列值空間足夠大的時(shí)候,隨機(jī)碰撞忽略不計(jì),因此也就確保了node ID 的唯一性
? ? 2.2、可以簡化系統(tǒng)設(shè)計(jì)——比如簡化路由算法(下面會提及)
3、拓?fù)浣Y(jié)構(gòu)的設(shè)計(jì)
作為分布式系統(tǒng), DHT 必然要定義某種拓?fù)浣Y(jié)構(gòu);有了拓?fù)浣Y(jié)構(gòu),自然就要設(shè)計(jì)某種路由算法。
如果某個(gè)DHT 采用前面所說的node ID與data key同構(gòu),那么很自然的就會引入Key-based routing。
當(dāng)某個(gè)分布式系統(tǒng)具有自己的拓?fù)浣Y(jié)構(gòu),它本身成為一個(gè)Overlay網(wǎng)絡(luò)(Overlay Network)。所謂的Overlay網(wǎng)絡(luò),通俗地
說就是網(wǎng)絡(luò)之上的網(wǎng)絡(luò)。對于大部分 DHT 而言,它們是基于互聯(lián)網(wǎng)之上的覆蓋網(wǎng)絡(luò),它們的數(shù)據(jù)通訊是依賴下層的互聯(lián)網(wǎng)來實(shí)現(xiàn)
的。
前面提到的node ID,其解耦的作用就體現(xiàn)在分布式系統(tǒng)在設(shè)計(jì)拓?fù)浣Y(jié)構(gòu)和路由算法時(shí),只需要考慮 node ID,而不用考慮其下層網(wǎng)絡(luò)的屬性(比如:協(xié)議類型、 IP 地址、端?口號)。
4、路由算法的權(quán)衡
由于DHT中的節(jié)點(diǎn)數(shù)可能非常多(比如:幾十萬、幾百萬),而且這些節(jié)點(diǎn)是動態(tài)變化的。因此就不可能讓每一個(gè)節(jié)點(diǎn)都記錄所有其它節(jié)點(diǎn)的信息。實(shí)際情況是:每個(gè)節(jié)點(diǎn)通常只知道少數(shù)一些節(jié)點(diǎn)的信息。
這時(shí)候就需要設(shè)計(jì)某種路由算法,盡可能利用已知的節(jié)點(diǎn)來轉(zhuǎn)發(fā)數(shù)據(jù)。 路由算法很重要,直接決定了DHT 的速度和資源消耗。
在確定了路由算法之后,還需要做一個(gè)兩難的權(quán)衡路由表的大小。路由表越大,可以實(shí)現(xiàn)越短(跳數(shù)越少)的路由;缺點(diǎn)是:(由于節(jié)點(diǎn)動態(tài)變化)路由表的維護(hù)成本也就越高。路由表數(shù)越小,其維護(hù)成本越小;缺點(diǎn)是:路由就會變長(跳數(shù)變多)。
5、距離算法
某些 DHT 系統(tǒng)還會定義一種距離算法,用來計(jì)算: 節(jié)點(diǎn)之間的距離、 數(shù)據(jù)之間的距離、 節(jié)點(diǎn)與數(shù)據(jù)的距離。
請注意:此處所說的距離屬于邏輯層面,對應(yīng)的是 DHT 自己的拓?fù)浣Y(jié)構(gòu);它與地理位置無關(guān),也與互聯(lián)網(wǎng)的拓?fù)浣Y(jié)構(gòu)無關(guān)。
這里就能明白為什么前面要強(qiáng)調(diào)node ID與data key同構(gòu)。當(dāng)這兩者同構(gòu),就可以用同一種距離算法;反之,如果這兩者不同構(gòu),多半要引入幾種不同的距離算法。
6、數(shù)據(jù)定位
對 DHT 而言,這是最關(guān)鍵的。DHT 與傳統(tǒng)的散列表在功能上是類似的。說白了,他們最關(guān)鍵的功能只有兩個(gè)保存數(shù)據(jù)和獲取數(shù)據(jù)。
保存數(shù)據(jù)
(大致原理,具體的協(xié)議實(shí)現(xiàn)可能會有差異)
當(dāng)某個(gè)節(jié)點(diǎn)得到了新加入的數(shù)據(jù)(K/V),它會先計(jì)算自己與新數(shù)據(jù)的 key 之間的距離;然后再計(jì)算它所知道的其它節(jié)點(diǎn)與這個(gè)key 的距離。
如果計(jì)算下來,自己與 key 的距離最小,那么這個(gè)數(shù)據(jù)就保持在自己這里。
否則的話,把這個(gè)數(shù)據(jù)轉(zhuǎn)發(fā)給距離最小的節(jié)點(diǎn)。
收到數(shù)據(jù)的另一個(gè)節(jié)點(diǎn),也采用上述過程進(jìn)行處理(遞歸處理)。
獲取數(shù)據(jù)
(大致原理,具體的協(xié)議實(shí)現(xiàn)可能會有差異)
當(dāng)某個(gè)節(jié)點(diǎn)接收到查詢數(shù)據(jù)的請求(key),它會先計(jì)算自己與 key 之間的距離;然后再計(jì)算它所知道的其它節(jié)點(diǎn)與這個(gè) key 的距離。
如果計(jì)算下來,自己與 key 的距離最小,那么就在自己這里找有沒有 key 對應(yīng)的 value。有的話就返回 value,沒有的話就報(bào)錯(cuò)。
否則的話,把這個(gè)數(shù)據(jù)轉(zhuǎn)發(fā)給距離最小的節(jié)點(diǎn)。
收到數(shù)據(jù)的另一個(gè)節(jié)點(diǎn),也采用上述過程進(jìn)行處理(遞歸處理)。
Chord 協(xié)議
Chord 誕生于2001年。第一批 DHT 協(xié)議都是在那年涌現(xiàn)的,另外幾個(gè)是: CAN、 Tapestry、 Pastry。
拓?fù)浣Y(jié)構(gòu)——環(huán)形
Chord 的拓?fù)?#xff0c;必然要提到Consistent Hashing(譯作一致散列)。搞明白一致散列也就知道 Chord的拓?fù)湓O(shè)計(jì)了。
一致散列主要是為了解決節(jié)點(diǎn)動態(tài)變化這個(gè)難點(diǎn)。為了解決這個(gè)難點(diǎn), 一致散列把散列值空間(keyspace)構(gòu)成一個(gè)環(huán)。對于 m 比特的散列值,其范圍是 [0, 2m-1]。你把這個(gè)區(qū)間頭尾相接就變成一個(gè)環(huán),其周長是 2m。然后對這個(gè)環(huán)規(guī)定了一個(gè)移動方向(比如順時(shí)針)。
如果 node ID 和 data key 是同構(gòu)的,那么這兩者都可以映射到這個(gè)環(huán)上(對應(yīng)于環(huán)上的某點(diǎn))。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ????
?假設(shè)有某個(gè)節(jié)點(diǎn)A,距離它最近的是節(jié)點(diǎn)B(以順時(shí)針方向衡量距離)。那么稱 B 是 A 的繼任(successor), A 是 B 的前任(predecessor)。
數(shù)據(jù)隸屬于【距離最小】的節(jié)點(diǎn)。以 m=6 的環(huán)形空間為例:
? ? 數(shù)據(jù)區(qū)間 [5,8] 隸屬于節(jié)點(diǎn)8
? ? 數(shù)據(jù)區(qū)間 [9,15] 隸屬于節(jié)點(diǎn)15
? ? ......
? ? 數(shù)據(jù)區(qū)間 [59,4] 隸屬于節(jié)點(diǎn)4(注: 6比特的環(huán)形空間, 63之后是0)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
?路由機(jī)制
接下來簡單說?一下路由的玩法。
基本路由(簡單遍歷)
當(dāng)收到請求(key),先看 key 是否在自己這里。如果在自己這里,就直接返回信息;否則就把 key 轉(zhuǎn)發(fā)給自己的繼任者。以此類推。
這種玩法的時(shí)間復(fù)雜度是 O(N)。對于一個(gè)節(jié)點(diǎn)數(shù)很多的 DHT 網(wǎng)絡(luò),這種做法顯然非常低效。
高級路由(Finger Table)
由于基本路由非常低效,自然就引入更高級的玩法——基于“Finger Table”的路由。
? ? Finger Table是一個(gè)列表,最多包含 m 項(xiàng)(m 就是散列值的比特?cái)?shù)),每一項(xiàng)都是節(jié)點(diǎn) ID。
假設(shè)當(dāng)前節(jié)點(diǎn)的 ID 是 n,那么表中第 i 項(xiàng)的值是: successor( (n + 2i) mod 2m )
當(dāng)收到請求(key),就到Finger Table中找到最大的且不超過 key的那一項(xiàng),然后把 key 轉(zhuǎn)發(fā)給這一項(xiàng)對應(yīng)的節(jié)點(diǎn)。有了Finger Table之后,時(shí)間復(fù)雜度可以優(yōu)化為 O(log N)。
?節(jié)點(diǎn)的加入
1任何一個(gè)新來的節(jié)點(diǎn)(假設(shè)叫 A),需要先跟 DHT 中已有的任一節(jié)點(diǎn)(假設(shè)叫 B)建立連接。
2 A 隨機(jī)生成一個(gè)散列值作為自己的 ID(對于足夠大的散列值空間, ID 相同的概率忽略不計(jì))
3 A 通過跟 B 進(jìn)行查詢,找到自己這個(gè) ID 在環(huán)上的接頭人。也就是——找到自己這個(gè) ID 對應(yīng)的“繼任”(假設(shè)叫 C)與“前任”(假設(shè)叫 D)
4 接下來, A 需要跟 C 和 D 進(jìn)行一系列互動,使得自己成為 C 的前任,以及 D 的繼任。
這個(gè)互動過程,大致類似于在雙向鏈表當(dāng)中插入元素。
節(jié)點(diǎn)的正常退出
如果某個(gè)節(jié)點(diǎn)想要主動離開這個(gè) DHT 網(wǎng)絡(luò),按照約定需要作一些善后的處理工作。比如說,通知自己的前任去更新其繼任者......
這些善后處理,大致類似于在雙向鏈表中刪除元素。
節(jié)點(diǎn)的異常退出
作為一個(gè)分布式系統(tǒng),任何節(jié)點(diǎn)都有可能意外下線(也就是說,來不及進(jìn)行善后就掛掉了)
假設(shè) 節(jié)點(diǎn)A 的繼任者異常下線了,那么 節(jié)點(diǎn)A 就抓瞎了。
為了保險(xiǎn)起見, Chord 引入了一個(gè)繼任者候選列表”的概念。每個(gè)節(jié)點(diǎn)都用這個(gè)列表來包含:距離自己最近的 N 個(gè)節(jié)點(diǎn)的信息,順序是由近到遠(yuǎn)。一旦自己的繼任者下線了,就在列表中找到一個(gè)距離最近且在線的節(jié)點(diǎn),作為新的繼任者。然后 節(jié)點(diǎn)A 更新該列表,確保依然有 N 個(gè)候選。更新完繼任者候選列表后,節(jié)點(diǎn)A 也會通知自己的前任,那么 A 的前任也就能更新自己的繼任者候選列表。
Kademlia(Kad)協(xié)議?
Kad 的原理比Chord 稍微晦澀一些。之所以選 Kad 來介紹,是因?yàn)閷?shí)際應(yīng)用的 DHT大部分都采用 Kad 及其變種。
拓?fù)浣Y(jié)構(gòu)二叉樹
Kad 也采用了node ID 與 data key同構(gòu)的設(shè)計(jì)思路。然后 Kad 采用某種算法把 key 映射到一個(gè)二叉樹,每一個(gè) key 都是這個(gè)二叉樹的葉子。
在映射之前,先做一下預(yù)處理。
1. 先把 key 以二進(jìn)制形式表示。
2. 把每一個(gè) key 縮短為它的最短唯一前綴。
為什么要搞“最短唯一前綴”?
Kad 使用 160 比特的散列算法(比如 SHA1),完整的 key 用二進(jìn)制表示有 160 個(gè)數(shù)位。
首先,實(shí)際運(yùn)行的 Kad 網(wǎng)絡(luò),即使有幾百萬個(gè)節(jié)點(diǎn),相比 keyspace(2160)也只是很小的一個(gè)子集。
其次,由于散列函數(shù)的特點(diǎn), key 的分布是高度隨機(jī)的。因此也是高度離散的——任何兩個(gè) key 都不會非常臨近。
所以,使用最短唯一前綴來處理 key 的二進(jìn)制形式,得到的結(jié)果就會很短(遠(yuǎn)遠(yuǎn)小于 160 個(gè)數(shù)位)。
散列值的映射
完成上述的預(yù)處理后,接下來的映射規(guī)則是:
1. 先把 key 以二進(jìn)制形式表示,然后從高位到低位依次處理。
2. 二進(jìn)制的第 n 個(gè)數(shù)位就對應(yīng)了二叉樹的第 n 層
3. 如果該位是1,進(jìn)入左子樹,是0則進(jìn)入右子樹(這只是人為約定,反過來處理也可以)
4. 全部數(shù)位都處理完后,這個(gè) key 就對應(yīng)了二叉樹上的某個(gè)葉子
距離算法——異或(XOR)
接下來要聊的是 Kad 最精妙之處——采用 XOR(按比特異或操作)算法計(jì)算 key 之間的距離。
這種搞法使得它具備了類似于幾何距離的某些特性(下面用 ⊕ 表示 XOR)
路由機(jī)制
二叉樹的拆分
對每一個(gè)節(jié)點(diǎn),都可以按照自己的視角對整個(gè)二叉樹進(jìn)行拆分。
拆分的規(guī)則是:先從根節(jié)點(diǎn)開始,把不包含自己的那個(gè)子樹拆分出來;然后在剩下的子樹再拆分不包含自己的下一層子樹;以此類推,直到最后只剩下自己。
Kad 默認(rèn)的散列值空間是 m=160(散列值有 160 比特),因此拆分出來的子樹最多有 160 個(gè)(考慮到實(shí)際的節(jié)點(diǎn)數(shù)遠(yuǎn)遠(yuǎn)小于2160,子樹的個(gè)數(shù)會明顯小于160)。
對于每一個(gè)節(jié)點(diǎn)而言,當(dāng)它以自己的視角完成子樹拆分后,會得到 n 個(gè)子樹;對于每個(gè)子樹,如果它都能知道里面的一個(gè)節(jié)點(diǎn),那么它就可以利用這 n個(gè)節(jié)點(diǎn)進(jìn)行遞歸路由,從而到達(dá)整個(gè)二叉樹的任何一個(gè)節(jié)點(diǎn)(考慮到篇幅,具體的數(shù)學(xué)證明就不貼出來了)
K-桶(K-bucket)
前面說了,每個(gè)節(jié)點(diǎn)在完成子樹拆分后,只需要知道每個(gè)子樹里面的一個(gè)節(jié)點(diǎn),就足以實(shí)現(xiàn)全遍歷。但是考慮到健壯性(分布式系統(tǒng)的節(jié)點(diǎn)是動態(tài)變化的),光知道一個(gè)是不夠滴,需要知道多個(gè)才比較保險(xiǎn)。
所以 Kad 論?文中給出了一個(gè)K-桶(K-bucket)的概念。也就是說:每個(gè)節(jié)點(diǎn)在完成子樹拆分后,要記錄每個(gè)子樹里面的 K 個(gè)節(jié)點(diǎn)。這里所說的 K 值是一個(gè)系統(tǒng)級的常量。由使用 Kad 的軟件系統(tǒng)自己設(shè)定(比如 BT 下載使用的 Kad 網(wǎng)絡(luò), K 設(shè)定為 8)。
K 桶其實(shí)就是路由表。對于某個(gè)節(jié)點(diǎn)而言,如果以它為視角拆分了n 個(gè)子樹,那么它就需要維護(hù) n 個(gè)路由表,并且每個(gè)路由表的上限是 K。
說 K 只是一個(gè)上限,是因?yàn)橛袃煞N情況使得 K 桶的尺寸會小于 K。
1. 距離越近的子樹就越小。如果整個(gè)子樹可能存在的節(jié)點(diǎn)數(shù)小于 K,那么該子樹的 K 桶尺寸永遠(yuǎn)也不可能達(dá)到 K。
2. 有些子樹雖然實(shí)際上線的節(jié)點(diǎn)數(shù)超過 K,但是因?yàn)榉N種原因,沒有收集到該子樹足夠多的節(jié)點(diǎn),這也會使得該子樹的 K 桶尺寸小于K。
應(yīng)用實(shí)例
1、文件的存儲及查找
原來收藏在圖書館里,按索引號碼得整整齊齊的書,以一種什么樣的方式分發(fā)到同學(xué)們手里呢?大致的原則,包括:1)書本能夠比較均衡地分布在同學(xué)們的手里,不會出現(xiàn)部分同學(xué)手里書特別多、而大部分同學(xué)連一本書都沒有的情況;2)同學(xué)想找一本特定的書的時(shí)候,能夠一種相對簡單的索引方式找到這本書。
Kademlia作了下面這種安排:
假設(shè)《分布式算法》這本書的書名的hash值是?00010000,那么這本書就會被要求存在學(xué)號為00010000的同學(xué)手上。(這要求hash算法的值域與node ID的值域一致。Kademlia的Node ID是160位2進(jìn)制。這里的示例對Node ID進(jìn)行了簡略)
但還得考慮到會有同學(xué)缺勤。萬一00010000今天沒來上學(xué)(節(jié)點(diǎn)沒有上線或徹底退出網(wǎng)絡(luò)),那《分布式算法》這本書豈不是誰都拿不到了?那算法要求這本書不能只存在一個(gè)同學(xué)手上,而是被要求同時(shí)存儲在學(xué)號最接近00010000的k位同學(xué)手上,即00010001、00010010、00010011…等同學(xué)手上都會有這本書。
同樣地,當(dāng)你需要找《分布式算法》這本書時(shí),將書名hash一下,得到?00010000,這個(gè)便是索書號,你就知道該找哪(幾)位同學(xué)了。剩下的問題,就是找到這(幾)位同學(xué)的手機(jī)號。
2、節(jié)點(diǎn)的異或距離
由于你手上只有一部分同學(xué)的通訊錄,你很可能并沒有00010000的手機(jī)號(IP地址)。那如何聯(lián)系上目標(biāo)同學(xué)呢?
一個(gè)可行的思路就是在你的通訊錄里找到一位擁有目標(biāo)同學(xué)的聯(lián)系方式的同學(xué)。前面提到,每位同學(xué)手上的通訊錄都是按距離分層的。算法的設(shè)計(jì)是,如果一個(gè)同學(xué)離你越近,你手上的通訊錄里存有ta的手機(jī)號碼的概率越大。而算法的核心的思路就可以是:當(dāng)你知道目標(biāo)同學(xué)Z與你之間的距離,你可以在你的通訊錄上先找到一個(gè)你認(rèn)為與同學(xué)Z最相近的同學(xué)B,請同學(xué)B再進(jìn)一步去查找同學(xué)Z的手機(jī)號。
上文提到的距離,是學(xué)號(Node ID)之間的異或距離(XOR distance)。異或是針對yes/no或者二進(jìn)制的運(yùn)算.
舉2個(gè)例子:
01010000與01010010距離(即是2個(gè)ID的異或值)為00000010(換算為十進(jìn)制即為2);
01000000與00000001距離為01000001(換算為十進(jìn)制即為26+1,即65);
如此類推。
那通訊錄是如何按距離分層呢?下面的示例會告訴你,按異或距離分層,基本上可以理解為按位數(shù)分層。設(shè)想以下情景:
以0000110為基礎(chǔ)節(jié)點(diǎn),如果一個(gè)節(jié)點(diǎn)的ID,前面所有位數(shù)都與它相同,只有最后1位不同,這樣的節(jié)點(diǎn)只有1個(gè)——0000111,與基礎(chǔ)節(jié)點(diǎn)的異或值為0000001,即距離為1;對于0000110而言,這樣的節(jié)點(diǎn)歸為“k-bucket 1”;
如果一個(gè)節(jié)點(diǎn)的ID,前面所有位數(shù)相同,從倒數(shù)第2位開始不同,這樣的節(jié)點(diǎn)只有2個(gè):0000101、0000100,與基礎(chǔ)節(jié)點(diǎn)的異或值為0000011和0000010,即距離范圍為3和2;對于0000110而言,這樣的節(jié)點(diǎn)歸為“k-bucket 2”;
……
如果一個(gè)節(jié)點(diǎn)的ID,前面所有位數(shù)相同,從倒數(shù)第n位開始不同,這樣的節(jié)點(diǎn)只有2(i-1)個(gè),與基礎(chǔ)節(jié)點(diǎn)的距離范圍為[2(i-1), 2i);對于0000110而言,這樣的節(jié)點(diǎn)歸為“k-bucket i”;
?
按位數(shù)區(qū)分k-bucket
對上面描述的另一種理解方式:如果將整個(gè)網(wǎng)絡(luò)的節(jié)點(diǎn)梳理為一個(gè)按節(jié)點(diǎn)ID排列的二叉樹,樹最末端的每個(gè)葉子便是一個(gè)節(jié)點(diǎn),則下圖就比較直觀的展現(xiàn)出,節(jié)點(diǎn)之間的距離的關(guān)系。
k-bucket示意圖:右下角的黑色實(shí)心圓,為基礎(chǔ)節(jié)點(diǎn)(按wiki百科的配圖修改)
回到我們的類比。每個(gè)同學(xué)只維護(hù)一部分的通訊錄,這個(gè)通訊錄按照距離分層(可以理解為按學(xué)號與自己的學(xué)號從第幾位開始不同而分層),即k-bucket1, k-bucket 2, k-bucket 3…雖然每個(gè)k-bucket中實(shí)際存在的同學(xué)人數(shù)逐漸增多,但每個(gè)同學(xué)在它自己的每個(gè)k-bucket中只記錄k位同學(xué)的手機(jī)號(k個(gè)節(jié)點(diǎn)的地址與端口,這里的k是一個(gè)可調(diào)節(jié)的常量參數(shù))。
由于學(xué)號(節(jié)點(diǎn)的ID)有160位,所以每個(gè)同學(xué)的通訊錄中共分160層(節(jié)點(diǎn)共有160個(gè)k-bucket)。整個(gè)網(wǎng)絡(luò)最多可以容納2^160個(gè)同學(xué)(節(jié)點(diǎn)),但是每個(gè)同學(xué)(節(jié)點(diǎn))最多只維護(hù)160 * k 行通訊錄(其他節(jié)點(diǎn)的地址與端口)。
3、節(jié)點(diǎn)定位
我們現(xiàn)在來闡述一個(gè)完整的索書流程。
A同學(xué)(學(xué)號00000110)想找《分布式算法》,A首先需要計(jì)算書名的哈希值,hash(《分布式算法》) =?00010000。那么A就知道ta需要找到00010000號同學(xué)(命名為Z同學(xué))或?qū)W號與Z鄰近的同學(xué)。
Z的學(xué)號00010000與自己的異或距離為?00010110,距離范圍在[24, 25),所以這個(gè)Z同學(xué)可能在k-bucket 5中(或者說,Z同學(xué)的學(xué)號與A同學(xué)的學(xué)號從第5位開始不同,所以Z同學(xué)可能在k-bucket 5中)。
然后A同學(xué)看看自己的k-bucket 5有沒有Z同學(xué):
- 如果有,那就直接聯(lián)系Z同學(xué)要書;
- 如果沒有,在k-bucket 5里隨便找一個(gè)B同學(xué)(注意任意B同學(xué),它的學(xué)號第5位肯定與Z相同,即它與Z同學(xué)的距離會小于24,相當(dāng)于比Z、A之間的距離縮短了一半以上),請求B同學(xué)在它自己的通訊錄里按同樣的查找方式找一下Z同學(xué):
-- 如果B知道Z同學(xué),那就把Z同學(xué)的手機(jī)號(IP Address)告訴A;
-- 如果B也不知道Z同學(xué),那B按同樣的搜索方法,可以在自己的通訊錄里找到一個(gè)離Z更近的C同學(xué)(Z、C之間距離小于23),把C同學(xué)推薦給A;A同學(xué)請求C同學(xué)進(jìn)行下一步查找。
查詢方式示意
Kademlia的這種查詢機(jī)制,有點(diǎn)像是將一張紙不斷地對折來收縮搜索范圍,保證對于任意n個(gè)學(xué)生,最多只需要查詢log2(n)次,即可找到獲得目標(biāo)同學(xué)的聯(lián)系方式(即在對于任意一個(gè)有[2(n?1), 2n)個(gè)節(jié)點(diǎn)的網(wǎng)絡(luò),最多只需要n步搜索即可找到目標(biāo)節(jié)點(diǎn))。
每次搜索都將距離至少收縮一半
以上便是Kademlia算法的基本原理。以下再簡要介紹協(xié)議中的技術(shù)細(xì)節(jié)。
4、算法的三個(gè)參數(shù):keyspace,k和α
keyspace
-- 即ID有多少位
-- 決定每個(gè)節(jié)點(diǎn)的通訊錄有幾層
k
-- 每個(gè)一層k-bucket里裝k個(gè)node的信息,即
-- 每次查找node時(shí),返回k個(gè)node的信息
-- 對于某個(gè)特定的data,離其key最近的k個(gè)節(jié)點(diǎn)被會要求存儲這個(gè)data
α
-- 每次向其他node請求查找某個(gè)node時(shí),會向α個(gè)node發(fā)出請求
5、節(jié)點(diǎn)的指令
Kademlia算法中,每個(gè)節(jié)點(diǎn)只有4個(gè)指令
PING
-- 測試一個(gè)節(jié)點(diǎn)是否在線
STORE
-- 要求一個(gè)節(jié)點(diǎn)存儲一份數(shù)據(jù)
FIND_NODE
-- 根據(jù)節(jié)點(diǎn)ID查找一個(gè)節(jié)點(diǎn)
FIND_VALUE
-- 根據(jù)KEY查找一個(gè)數(shù)據(jù),實(shí)則上跟FIND_NODE非常類似
6、k-bucket的維護(hù)及更新機(jī)制
刷新機(jī)制大致有如下幾種:
1. 主動收集節(jié)點(diǎn)
任何節(jié)點(diǎn)都可以主動發(fā)起“查詢節(jié)點(diǎn)”的請求(對應(yīng)于協(xié)議類型 FIND_NODE),從而刷新 K 桶中的節(jié)點(diǎn)信息(下面聊“節(jié)點(diǎn)的加入”時(shí),會提及這種)
2. 被動收集節(jié)點(diǎn)
如果收到其它節(jié)點(diǎn)發(fā)來的請求(協(xié)議類型 FIND_NODE 或 FIND_VALUE),會把對方的 ID 加入自己的某個(gè) K 桶中。
3. 探測失效節(jié)點(diǎn)
? ? Kad 還是支持一種探測機(jī)制(協(xié)議類型 PING),可以判斷某個(gè) ID 的節(jié)點(diǎn)是否在線。因此就可以定期探測路由表中的每一個(gè)節(jié)點(diǎn),然后把下線的節(jié)點(diǎn)從路由表中干掉。
7、節(jié)點(diǎn)的加入
7.1
任何一個(gè)新來的節(jié)點(diǎn)(假設(shè)叫 A),需要先跟 DHT 中已有的任一節(jié)點(diǎn)(假設(shè)叫 B)建立連接。
7.2
? ? A 隨機(jī)生成一個(gè)散列值作為自己的 ID(對于足夠大的散列值空間, ID 相同的概率忽略不計(jì))
7.3
? ? A 向 B 發(fā)起一個(gè)查詢請求(協(xié)議類型 FIND_NODE),請求的 ID 是自己(通俗地說,就是查詢自己)
7.4
? ? B 收到該請求之后,(如前面所說)會先把 A 的 ID 加入自己的某個(gè) K 桶中。
然后,根據(jù) FIND_NODE 協(xié)議的約定, B 會找到K個(gè)最接近 A 的節(jié)點(diǎn),并返回給 A。
(B 怎么知道哪些節(jié)點(diǎn)接近 A 捏?這時(shí)候,用 XOR 表示距離的算法就發(fā)揮作用啦)
7.5
? ? A 收到這 K 個(gè)節(jié)點(diǎn)的 ID 之后,(僅僅根據(jù)這批 ID 的值)就可以開始初始化自己的 K 桶。
7.6
然后 A 會繼續(xù)向剛剛拿到的這批節(jié)點(diǎn)發(fā)送查詢請求(協(xié)議類型 FIND_NODE),如此往復(fù)(遞歸),直至 A 建立了足夠詳細(xì)的路由表。
8、節(jié)點(diǎn)的退出
?? ?與 Chord 不同, Kad 對于節(jié)點(diǎn)退出沒有額外的要求(沒有“主動退出”的說法)。
所以, Kad 的節(jié)點(diǎn)想離開 DHT 網(wǎng)絡(luò)不需要作任何操作
總結(jié):
Kad 成為 DHT 的主流實(shí)現(xiàn)?方式,這已經(jīng)是很明顯的事實(shí)。
?
總結(jié)
以上是生活随笔為你收集整理的分布式散列表(DHT)及具体实现Kademlia(kad)/Chord的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何获得通信达交易接口?
- 下一篇: 艾宾浩斯记忆遗忘曲线