死磕以太坊源碼分析之Kademlia算法


死磕以太坊源碼分析之Kademlia算法

KAD 算法概述

Kademlia是一種點對點分布式哈希表(DHT),它在容易出錯的環境中也具有可證明的一致性和性能。使用一種基於異或指標的拓撲結構來路由查詢和定位節點,這簡化了算法並有助於證明。該拓撲結構有一個特點:每次消息交換都能夠傳遞或強化有效信息。系統利用這些信息進行並發的異步查詢,可以容忍節點故障,並且故障不會導致用戶超時。

KAD算法要處理的問題

  1. 如何分配存儲內容到各個節點,新增/刪除內容如何處理
  2. 如何找到存儲文件的節點/地址/路徑

節點狀態

節點的基本屬性包括如下:

  • 節點ID,Node ID
  • 節點IP地址與端口號

在 Kad 網絡中,所有節點都被當作一顆二叉樹的葉子,並且每一個節點的位置都由其 ID 值的最短前綴唯一的確定。

對於任意一個節點,都可以把這顆二叉樹分解為一系列連續的,不包含自己的子樹。最高層的子樹,由整顆樹不包含自己的樹的另一半組成;下一層子樹由剩下部分不包含自己的一半組成;依此類推,直到分割完整顆樹。圖 1 就展示了節點0011如何進行子樹的划分:

image-20201122115058412

虛線包含的部分就是各子樹,由上到下各層的前綴分別為0,01,000,0010。

Kad 協議確保每個節點知道其各子樹的至少一個節點,只要這些子樹非空。在這個前提下,每個節點都可以通過ID值來找到任何一個節點。這個路由的過程是通過所謂的 XOR(異或)距離得到的。

圖 2 就演示了節點0011如何通過連續查詢來找到節點1110的。節點0011通過在逐步底層的子樹間不斷學習並查詢最佳節點,獲得了越來越接近的節點,最終收斂到目標節點上。

image-20201122115359426

需要說明的是:只有第一步查詢的節點101,是節點0011已經知道的,后面各步查詢的節點,都是由上一步查詢返回的更接近目標的節點,這是一個遞歸操作的過程


節點距離

Kad 網絡中每個節點都有一個 160 bit 的 ID 值作為標志符,Key 也是一個 160 bit 的標志符,每一個加入 Kad 網絡的計算機都會在 160 bit 的 key 空間被分配一個節點 ID(node ID)值(可以認為 ID 是隨機產生的), <key,value> 對的數據就存放在 ID 值“最”接近 key 值的節點上。

判斷兩個節點 x,y 的距離遠近是基於數學上的異或的二進制運算, d(x,y)=x⊕y ,既對應位相同時結果為0,不同時結果為1。例如:

    010101
XOR 110001
----------
    100100

則這兩個節點的距離為 32+4=36 。

顯然,高位上數值的差異對結果的影響更大。

對於異或操作,有如下一些數學性質:

  • 兩個節點間的距離是隨機的
  • 節點與自身的距離是0
  • 對稱性。A 到 B 的距離和 B 到 A 的距離相等
  • 三角不等。distance(A,B)+distance(B,C) <= distance(A,C)

對於任意給定的節點 x 和距離 Δ≥0 ,總會存在一個精確的節點 y ,使得 d(x,y)=Δ 。另外,單向性也確保了對於同一個 key 值的所有查詢都會逐步收斂到同一個路徑上,而不管查詢的起始節點位置如何。這樣,只要沿着查詢路徑上的節點都緩存這個 <key,value> 對,就可以減輕存放熱門 key 值節點的壓力,同時也能夠加快查詢響應速度。

K桶

K 桶的概念

Kad 的路由表是通過一些稱之為 K 桶的表格構造起來的。

對每一個 0≤i≤160 ,每個節點都保存有一些和自己距離范圍在區間 [2i,2i+1) 內的一些節點信息,這些信息由一些 (IP address,UDP port,Node ID) 數據列表構成(Kad 網絡是靠 UDP 協議交換信息的)。每一個這樣的列表都稱之為一個 K 桶,並且每個 K 桶內部信息存放位置是根據上次看到的時間順序排列,最近( least-recently)看到的放在頭部,最后(most-recently)看到的放在尾部。每個桶都有不超過 k 個的數據項。

一個節點的全部 K 桶列表如下圖 所示:

image-20201122143202816

當 i 值很小時,K 桶通常是空的(也就是說沒有足夠多的節點,比如當 i = 0 時,就最多可能只有1項);而當 i 值很大時,其對應 K 桶的項數又很可能會超過 k 個(當然,覆蓋距離范圍越廣,存在較多節點的可能性也就越大),這里 k 是為平衡系統性能和網絡負載而設置的一個常數,但必須是偶數,比如 k = 20。在 BitTorrent 的實現中,取值為 k = 8。

由於每個 K 桶覆蓋距離的范圍呈指數關系增長,這就形成了離自己近的節點的信息多,離自己遠的節點的信息少,從而可以保證路由查詢過程是收斂。因為是用指數方式划分區間,經過證明,對於一個有 N 個節點的 Kad 網絡,最多只需要經過 logN 步查詢,就可以准確定位到目標節點。

K桶更新機制

當節點 x 收到一個 PRC 消息時,發送者 y 的 IP 地址就被用來更新對應的 K 桶,具體步驟如下:

  1. 計算自己和發送者的距離: d(x,y)=x⊕y ,注意:x 和 y 是 ID 值,不是 IP 地址
  2. 通過距離 d 選擇對應的 K 桶進行更新操作
  3. 如果 y 的 IP 地址已經存在於這個 K 桶中,則把對應項移到該該 K 桶的尾部
  4. 如果 y 的 IP 地址沒有記錄在該 K 桶中
    1. 如果該 K 桶的記錄項小於 k 個,則直接把 y 的 (IP address, UDP port, Node ID) 信息插入隊列尾部
    2. 如果該 K 桶的記錄項大於 k 個,則選擇頭部的記錄項(假如是節點 z)進行 RPC_PING 操作
      1. 如果 z 沒有響應,則從 K 桶中移除 z 的信息,並把 y 的信息插入隊列尾部
      2. 如果 z 有響應,則把 z 的信息移到隊列尾部,同時忽略 y 的信息

K 桶的更新機制非常高效的實現了一種把最近看到的節點更新的策略,除非在線節點一直未從 K 桶中移出過。也就是說在線時間長的節點具有較高的可能性繼續保留在 K 桶列表中。

所以,通過把在線時間長的節點留在 K 桶里,Kad 就明顯增加 K 桶中的節點在下一時間段仍然在線的概率,這對應 Kad 網絡的穩定性和減少網絡維護成本(不需要頻繁構建節點的路由表)帶來很大好處。

這種機制的另一個好處是能在一定程度上防御 DOS 攻擊,因為只有當老節點失效后,Kad 才會更新 K 桶的信息,這就避免了通過新節點的加入來泛洪路由信息。

為了防止 K 桶老化,所有在一定時間之內無更新操作的 K 桶,都會分別從自己的 K 桶中隨機選擇一些節點執行 RPC_PING 操作。

上述這些 K 桶機制使 Kad 緩和了流量瓶頸(所有節點不會同時進行大量的更新操作),同時也能對節點的失效進行迅速響應。


協議消息

Kademlia 協議包括四種遠程 RPC 操作:PING、STORE、FIND_NODE、FIND_VALUE。

  1. PING 操作的作用是探測一個節點,用以判斷其是否仍然在線。

  2. STORE 操作的作用是通知一個節點存儲一個 <key,value> 對,以便以后查詢需要。

  3. FIND_NODE 操作使用一個 160 bit 的 ID 作為參數。本操作的接受者返回它所知道的更接近目標 ID 的 K 個節點的 (IP address, UDP port, Node ID) 信息。

    這些節點的信息可以是從一個單獨的 K 桶獲得,也可以從多個 K 桶獲得(如果最接近目標 ID 的 K 桶未滿)。不管是哪種情況,接受者都將返回 K 個節點的信息給操作發起者。但如果接受者所有 K 桶的節點信息加起來也沒有 K 個,則它會返回全部節點的信息給發起者。

  4. FIND_VALUE 操作和 FIND_NODE 操作類似,不同的是它只需要返回一個節點的 (IP address, UDP port, Node ID) 信息。如果本操作的接受者收到同一個 key 的 STORE 操作,則會直接返回存儲的 value 值。

    注:在 Kad 網絡中,系統存儲的數據以 <key,value> 對形式存放。根據筆者的分析,在 BitSpirit 的 DHT 實現中,其 key 值為 torrent 文件的 info_hash 串,其 value 值則和 torrent 文件有密切關系。

為了防止偽造地址,在所有 RPC 操作中,接受者都需要響應一個隨機的 160 bit 的 ID 值。另外,為了確信發送者的網絡地址,PING 操作還可以附帶在接受者的 RPC 回復信息中(在上述 4種操作中 接受者回復 發送者時,可以攜帶上 接受者對 發送者的 PING, 以此校驗 發送者是否還健在)。


路由查找

Kad 技術的最大特點之一就是能夠提供快速的節點查找機制,並且還可以通過參數進行查找速度的調節。

假如節點 x 要查找 ID 值為 t 的節點,Kad 按照如下遞歸操作步驟進行路由查找:

  1. 計算到 t 的距離: d(x,y)=x⊕y
  2. 從 x 的第 [logd] 個 K 桶中取出 α 個節點的信息(“[”“]”是取整符號),同時進行 FIND_NODE 操作。如果這個 K 桶中的信息少於 α 個,則從附近多個桶中選擇距離最接近 d 的總共 α 個節點。
  3. 對接受到查詢操作的每個節點,如果發現自己就是 t,則回答自己是最接近 t 的;否則測量自己和 t 的距離,並從自己對應的 K 桶中選擇 α 個節點的信息給 x。
  4. X 對新接受到的每個節點都再次執行 FIND_NODE 操作,此過程不斷重復執行,直到每一個分支都有節點響應自己是最接近 t 的。
  5. 通過上述查找操作,x 得到了 k 個最接近 t 的節點信息。

注意:這里用“最接近”這個說法,是因為 ID 值為 t 的節點不一定存在網絡中,也就是說 t 沒有分配給任何一台電腦。

這里 α 也是為系統優化而設立的一個參數,就像 K 一樣。在 BitTorrent 實現中,取值為 α=3 。

當 α=1 時,查詢過程就類似於 Chord 的逐跳查詢過程,如圖 4。

image-20201122133505567

整個路由查詢過程是遞歸操作的,其過程可用數學公式表示為:

N0=x (即查詢操作的發起者)

N1=find ⎯noden0(t)

N2=find ⎯noden1(t)

... ...

Nl=find ⎯nodenl−1(t)

這個遞歸過程一直持續到 Nl=t ,或者 Nl 的路由表中沒有任何關於 t 的信息,即查詢失敗。

由於每次查詢都能從更接近 t 的 K 桶中獲取信息,這樣的機制保證了每一次遞歸操作都能夠至少獲得距離減半(或距離減少 1 bit)的效果,從而保證整個查詢過程的收斂速度為 O(logN) ,這里 N 為網絡全部節點的數量。

當節點 x 要查詢 <key,value> 對時,和查找節點的操作類似,x 選擇 k 個 ID 值最接近 key 值的節點,執行 FIND_VALUE 操作,並對每一個返回的新節點重復執行 FIND_VALUE 操作,直到某個節點返回 value 值。

一旦 FIND_VALUE 操作成功執行,則 <key,value> 對數據會緩存在沒有返回 value 值的最接近的節點上。這樣下一次查詢相同的 key 時就會更加快速的得到結果。通過這樣的方式,熱門 <key,value> 對數據的緩存范圍就逐步擴大,使系統具有極佳的響應速度( cache 為存活24小時,但是目標節點上的內容時每1小時向其他最近節點重新發布<key, value>使得數據的超時時間得以刷新,而遠離目標節點的節點的數據存活時間當然就可能不會被重新發布到,所以也就是數據緩存的超時時間和節點的距離成反比)


數據存儲

存放 <key,value> 對數據的過程為:

  1. 發起者首先定位 k 個 ID 值最接近 key 的節點
  2. 發起者對這 k 個節點發起 STORE 操作
  3. 執行 STORE 操作的 k 個節點每小時重發布自己所有的 <key,value> 對數據
  4. 為了限制失效信息,所有 <key,value> 對數據在初始發布24小時后過期

另外,為了保證數據發布、搜尋的一致性,規定在任何時候,當節點 w 發現新節點 u 比 w 上的某些 <key,value> 對數據更接近,則 w 把這些 <key,value> 對數據復制到 u 上,但是並不會從 w 上刪除。


節點的加入和離開

如果節點 u 要想加入 Kad 網絡,它必須要和一個已經在 Kad 網絡的節點,比如 w,取得聯系。

u 首先把 w 插入自己適當的 K 桶中,然后對自己的節點 ID 執行一次 FIND_NODE 操作 (向 w 發布 查找 u 的 FIND_NODE 請求),然后根據接收到的信息更新自己的 K 桶內容。通過對自己鄰近節點由近及遠的逐步查詢,u 完成了仍然是空的 K 桶信息的構建,同時也把自己的信息發布到其他節點的 K 桶中。

節點 u 為例,其路由表的生成過程為:

  1. 最初,u 的路由表為一個單個的 K 桶,覆蓋了整個 160 bit ID 空間,如圖 6 最上面的路由表;
  2. 當學習到新的節點信息后,則 u 會嘗試把新節點的信息,根據其前綴值插入到對應的 K 桶中:
    1. 如果該 K 桶沒有滿,則新節點直接插入到這個 K 桶中;
    2. 如果該 K 桶已經滿了,
      1. 如果該 K 桶覆蓋范圍包含了節點 u 的 ID,則把該 K 桶分裂為兩個大小相同的新 K 桶,並對原 K 桶內的節點信息按照新的 K 桶前綴值進行重新分配
      2. 如果該 K 桶覆蓋范圍沒有包節點 u 的 ID,則直接丟棄該新節點信息
  3. 上述過程不斷重復,最終會形成表 1 結構的路由表。達到距離近的節點的信息多,距離遠的節點的信息少的結果,保證了路由查詢過程能快速收斂。

image-20201122145547341

在圖 7 中,演示了當覆蓋范圍包含自己 ID 值的 K 桶是如何逐步分裂的。

image-20201122145609681

當 K 桶 010 滿了之后,由於其覆蓋范圍包含了節點 0100 的 ID,故該 K 桶分裂為兩個新的 K 桶:0101 和 0100,原 K 桶 010 的信息會根據其其前綴值重新分布到這兩個新的 K 桶中。注意,這里並沒有使用 160 bit 的 ID 值表示法,只是為了方便原理的演示,實際 Kad 網絡中的 ID 值都是 160 bit 的。

節點離開 Kad 網絡不需要發布任何信息,Kademlia 協議的目標之一就是能夠彈性工作在任意節點隨時失效的情況下。為此,Kad 要求每個節點必須周期性 【一般是: 每小時】 的發布全部自己存放的 <key,value> 對數據,並把這些數據緩存在自己的 k 個最近鄰居處,這樣存放在失效節點的數據會很快被更新到其他新節點上。所以有節點離開了,那么就離開了,而且節點中的k-桶刷新機制也能保證會把已經不在線的節點信息從自己本地k-桶中移除


參考

https://github.com/blockchainGuide ☆☆☆☆☆

https://mindcarver.cn/ ☆☆☆☆☆

https://blog.csdn.net/qq_25870633/article/details/81939101

[http://www.ic.unicamp.br/bit/ensino/mo809_1s13/papers/P2P/Kademlia-%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%20.pdf](http://www.ic.unicamp.br/bit/ensino/mo809_1s13/papers/P2P/Kademlia- A Peer-to-Peer Information System Based on the XOR Metric .pdf)

https://blog.csdn.net/hoping/article/details/5307320

https://www.jianshu.com/p/f2c31e632f1d

https://www.ic.unicamp.br/~bit/ensino/mo809_1s13/papers/P2P/Kademlia- A Peer-to-Peer Information System Based on the XOR Metric .pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM