前两天在|上看到世界知名的电骡服务器Razorback 2被查?拘禁的消息,深感当前做eMule / BitTorrent{P2P文g交换软g的不易。以分布式哈希表方式(DHTQDistributed Hash Table)来代曉K中烦引服务器可以说是目前可以预见到的为数不多的P2P软g发展势之一Q比较典型的Ҏ主要包括QCAN、CHORD、Tapestry、Pastry、Kademlia和Viceroy{,而Kademlia协议则是其中应用最为广泛、原理和实现最为实用、简z的一U,当前L的P2P软g无一例外地采用了它作q辅助索协议,如eMule、Bitcomet、Bitspirit和Azureus{。鉴于Kademlia日益增长的强大媄响力Q今天特地在blog里写下这小文,是对其相关知识pȝ的ȝ?


1. Kademliaq?/b>

Kademlia(UKad)属于一U典型的l构化P2P覆盖|络(Structured P2P Overlay Network)Q以分布式的应用层全|方式来q行信息的存储和索是其尝试解决的主要问题。在Kademlia|络中,所有信息均?key, value="" />的哈希表条目形式加以存储Q这些条目被分散地存储在各个节点上,从而以全网方式构成一张巨大的分布式哈希表。我们可以Ş象地把这张哈希大表看成是一本字典:只要知道了信息烦引的keyQ我们便可以通过Kademlia协议来查询其所对应的value信息Q而不这个value信息I竟是存储在哪一个节点之上。在eMule、BitTorrent{P2P文g交换pȝ中,Kademlia主要充当了文件信息检索协议这一关键角色Q但Kad|络的应用ƈ不仅限于文g交换。下文的描述主要围leMule中Kad|络的设计与实现展开?/p>


2. eMule的Kad|络中究竟存储了哪些信息?

只要是能够表q成?key, value="" />字典条目形式的信息Kad|络均能存储Q一个Kad|络能够同时存储多张分布式哈希表。以eMuleZQ在M时刻Q其Kad|络均存储ƈl护着两张分布式哈希表Q一张我们可以将其命名ؓ关键词字典,而另一张则可以UC为文件烦引字典?/p>

a. 关键词字?/b>Q主要用于根据给出的关键词查询其所对应的文件名U及相关文g信息Q其中key的值等于所l出的关键词字符串的160比特SHA1散列Q而其对应的value则ؓ一个列表,在这个列表当中,l出了所有的文g名称当中拥有对应关键词的文g信息Q这些信息我们可以简单地用一?元组条目表示Q?文g名,文g长度Q文件的SHA1校验?QD个例子,假定存在着一个文件“warcraft_frozen_throne.iso”,当我们分别以“warcraft”、“frozen”、“throne”这三个关键词来查询KadӞKad有可能分别q回三个不同的文件列表,q三个列表的共同之处则在于它们均包含着一个文件名为“warcraft_frozen_throne.iso”的信息条目Q通过该条目,我们可以获得对应iso文g的名U、长度及?60比特的SHA1校验倹{?/p>

b. 文g索引字典Q用于根据给出的文g信息来查询文件的拥有?卌文g的下载服务提供?Q其中key的值等于所需下蝲文g的SHA1校验?q主要是因ؓQ从l计学角度而言Q?60比特的SHA1文g校验值可以唯一地确定一份特定数据内容的文g)Q而对应的value也是一个列表,它给Z当前所有拥有该文g的节点的|络信息Q其中的列表条目我们也可以用一?元组表示Q?拥有者IPQ下载侦听端口,拥有者节点ID)Q根据这些信息,eMule便知道该到哪里去下蝲具备同一SHA1校验值的同一份文件了?/p>


3. 利用Kad|络搜烦q下载文件的基本程是怎样?

Z我们对eMule的Kad|络中两本字典的理解Q利用Kad|络搜烦q下载某一特定文g的基本过E便很明白了Q仍以“warcraft_frozen_throne.iso”ؓ例,首先我们可以通过warcraft、frozen、throne{Q一关键词查询关键词字典Q得到该iso的SHA1校验|然后再通过该校验值查询Kad文g索引字典Q从而获得所有提供“warcraft_frozen_throne.iso”下载的|络节点Q而以分段下蝲方式去这些节点下载整个iso文g?/p>

在上q过E中QKad|络实际上所L作用q当于两本字典Q但值得再次指出的是QKadq不是以集中的烦引服务器(如华语P2P源动力、Razorback 2、DonkeyServer {,骡友们应该很熟悉?方式来实现这两本字典的存储和搜烦的,因ؓq两本字典的所?key, value="" />条目均分布式地存储在参与Kad|络的各节点中,相关文g信息、下载位|信息的存储和交换均无需集中索引服务器的参与Q这不仅提高了查询效率,而且q提高了整个P2P文g交换pȝ的可靠性,同时具备相当的反拒绝服务d能力Q更有意思的是,它能帮助我们有效地抵制FBI的追捕,因ؓ俗话说得好:法不M…看到这里,怿大家都能理解“分布式信息索”所带来的好处了吧。但是,q些条目I竟是怎样存储的呢?我们又该如何通过Kad|络来找到它?不着急,慢慢来?/p>


4. 什么叫做节点的ID和节点之间的距离?

Kad|络中的每一个节点均拥有一个专属IDQ该ID的具体Ş式与SHA1散列值类|Z个长?60bit的整敎ͼ它是p点自己随机生成的Q两个节Ҏ有同一ID的可能性非怹,因此可以认ؓq几乎是不可能的。在Kad|络中,两个节点之间距离q不是依靠物理距R\由器x来衡量的Q事实上QKad|络Q意两个节点之间的距离d定义为其二者ID值的逐比特二q制和数Q即Q假定两个节点的ID分别为a与bQ则有:d=a XOR b。在Kad中,每一个节炚w可以Ҏq一距离概念来判断其他节点距自q“远q”,当d值大Ӟ节点间距较q,而当d值小Ӟ则两个节点相距很q。这里的“远q”和“距Z都只是一U逻辑上的度量描述而已Q在Kad中,距离q一度量是无方向性的Q也是说a到b的距L{于b到a的距,因ؓa XOR b==b XOR a


5. 条目是如何存储在Kad|络中的?

从上文中我们可以发现节点ID?key, value="" />条目中key值的怼性:无论是关键词字典的keyQ还是文件烦引字典的keyQ都?60bitQ而节点ID恰恰也是160bit。这昄是有目的的。事实上Q节点的IDg决定了哪些条目可以存储在该节点之中Q因为我们完全可以把某一?key, value="" />条目单地存放在节点ID值恰好等于条目中key值的那个节点处,我们可以满?ID==key)q一条g的节点命名ؓ目标节点N。这L话,一个查?key, value="" />条目的问题便被简单地转化成ؓ了一个查找ID{于Key值的节点的问题?/p>

׃在实际的Kad|络当中Qƈ不能保证在Q一时刻目标节点N均一定存在或者在U,因此Kad|络规定QQ一条目Q依据其key的具体取|该条目将被复制ƈ存放在节点ID距离key值最q?卛_前距ȝ标节点N最q?的k个节点当中;之所以要?key, value="" />重复保存k份,q完全是考虑到整个KadpȝE_性而引入的冗余Q这个k的取g有讲IӞ它是一个带有启发性质的估计|挑选其取值的准则为:“在当前规模的Kad|络中Q意选择臛_k个节点,令它们在L时刻同时不在U的几率几乎?”;目前Qk的典型取gؓ20Q即Qؓ保证在Q何时L们均能找到至一份某条目的拷贝,我们必须事先在Kad|络中将该条目复制至?0份?/p>

׃q可知,对于某一条目Q在Kad|络中ID靠qkey的节点区域,该条目保存的份数p多,存储得也集中;事实上,Z实现较短的查询响应gq,在条目查询的q程中,M条目可被cacheCQ意节点之上;同时Z防止q度cache、保证信息够新鲜,必须考虑条目在节点上存储的时效性:接q目标结点NQ该条目保存的时间将长Q反之,其超时时间就短Q保存在目标节点之上的条目最多能够被保留24时Q如果在此期间该条目被其发布源重新发布的话,其保存时间还可以q一步gѝ?/p>


6. Kad|络节点需要维护哪些状态信?

在Kad|络中,每一个节点均l护?60个listQ其中的每个list均被UCZ个k-?k-bucket)Q如下图所C。在Wi个list中,记录了当前节点已知的与自w距Mؓ2^i~2^(i+1)的一些其他对端节点的|络信息(Node IDQIP地址QUDP端口)Q每一个list(k-?中最多存放k个对端节点信息,注意Q此处的k与上文所提到的复制系数k含义是一致的Q每一个list中的对端节点信息均按讉K旉排序Q最早访问的在list头部Q而最q新讉K的则攑֜list的尾部?/p>

k-桶中节点信息的更新基本遵循Least-recently Seen Eviction原则Q当list定w未满(k-桶中节点个数未满k?Q且最新访问的对端节点信息不在当前list中时Q其信息直接添入list队尾Q如果其信息已经在当前list中,则其被Ud至队;在k-桶容量已满的情况下,d新节点的情况有点ҎQ它首先检查最早访问的队首节点是否仍有响应Q如果有Q则队首节点被移至队,新访问节点信息被抛弃Q如果没有,q才抛弃队首节点Q将最新访问的节点信息插入队尾。可以看出,可能重用已有节点信息、ƈ且按旉排序是k-桶节Ҏ新方式的主要特点。从启发性的角度而言Q这U方式具有一定的依据Q在U时间长一点的节点更值得我们信QQ因为它已经在线了若q小Ӟ因此Q它在下一个小时以内保持在U的可能性将比我们最新访问的节点更大Q或者更直观点,我这里再l出一个更加h性化的解释:MP3文g交换本n是一U触犯版权法律的行ؓQ某一个节点反正已l犯了若q个时的法了,因此Q它比其他新加入的节点更不在乎再多犯一个小时的|…?_-b

׃可见Q设计采用这U多k-bucket数据l构的初衷主要有二:a. l护最q?最新见到的节点信息更新Qb. 实现快速的节点信息{选操作,也就是说Q只要知道某个需要查扄特定目标节点N的IDQ我们便可以从当前节点的k-bucketsl构中迅速地查出距离N最q的若干已知节点?/p>


7. 在Kad|络中如何寻找某特定的节?

已知某节点IDQ查找获得当前Kad|络中与之距L短的k个节Ҏ对应的网l信?Node IDQIP地址QUDP端口)的过E,即ؓKad|络中的一ơ节Ҏ询过E?Node Lookup)。注意,Kad之所以没有把节点查询q程严格地定义成Z仅只查询单个目标节点的过E,q主要是因ؓKad|络q没有对节点的上U时间作ZQ何前提假设,因此在多数情况下我们q不能肯定需要查扄目标节点一定在U或存在?/p>

整个节点查询q程非常直接Q其方式cM于DNS的P代查询:
a. 由查询发赯从自己的k-桶中{选出若干距离目标ID最q的节点Qƈ向这些节点同时发送异步查询请求;
b .被查询节Ҏ到请求之后,从自己的k-桶中扑և自己所知道的距L询目标ID最q的若干个节点,q返回给发v者;
c. 发v者在收到q些q回信息之后Q再ơ从自己目前所有已知的距离目标较近的节点中挑选出若干没有hq的Qƈ重复步骤1Q?br />d. 上述步骤不断重复Q直x法获得比查询者当前已知的k个节Ҏ接近目标的活动节点ؓ止?br />e. 在查询过E中Q没有及时响应的节点立卌排除Q查询者必M证最l获得的k个最q节炚w是活动的?/p>

单ȝ一下上q过E,实际上它跟我们日常生zML某一个h打听某g事是非常怼的,比方说你是个Agent SmithQ想扑ְ?key)问问他的手机L(value)Q但你事先ƈ不认识他Q你首先肯定会去找你所认识的和李在同一个公司工作的人,比方说小赵,然后n又会告诉你去找与和小李在同一部门的小刘,然后刘又会q一步告诉你L和小李在同一个项目组的小张,最后,你找C张Q哟Q正好小李出差去?节点下线?Q但张恰好知道李的号码,q样你ȝ扑ֈ了所需的信息。在节点查找的过E中Q“节点距ȝq近”实际上与上面例子中“h际关pȝ密切E度”所代表的含义是一L?br />

最后说说上q查询过E的局限性:Kad|络q不适合应用于模p搜索,如通配W支持、部分查扄场合Q但对于文g׃n场合来说Q基于关键词的精查扑֊能已l基本够了(值得注意的是Q实际上我们只要对上q查找过E稍加改q,q可以o其支持基于关键词匚w的布条件查询,但仍不够优化)。这个问题反映到eMule的应用层面来Q它直接说明了文件共享时其命名的重要性所在,卻I文g名中的关键词定义得越明显Q则该文件越Ҏ被找刎ͼ从而越有利于其在P2P|络中的传播Q而另一斚wQ在eMule中,每一个共享文件均可以拥有自己的相x释,而Comment的重要性还没有被大家认识到Q实际上Q这个文件注释中的关键词也可以直接被利用来替代文件名关键词,从而指导和方便用户搜烦Q尤其是当文件名本nq没有体现出关键词的时候?/p>


8. 在Kad|络中如何存储和搜烦某特定的条目?

从本质上而言Q存储、搜索某特定条目的问题实际上是节点查找的问题。当需要在Kad|络中存储一个条目时Q可以首先通过节点查找法扑ֈ距离key最q的k个节点,然后再通知它们保存条目卛_。而搜索条目的q程则与节点查询q程也是基本cMQ由搜烦发v方以q代方式不断查询距离key较近的节点,一旦查询\径中的Q一节点q回了所需查找的valueQ整个搜索的q程q束。ؓ提高效率Q当搜烦成功之后Q发h可以选择搜索到的条目存储到查询路径的多个节点中Q作为方便后l查询的cacheQ条目cache的超时时间与节点-key之间的距d指数反比关系?/p>


9. 一个新节点如何首次加入Kad|络?

当一个新节点首次试图加入Kad|络Ӟ它必d三g事,其一Q不通过何种途径Q获知一个已l加入Kad|络的节点信?我们可以UC点I)Qƈ其加入自己的k-bucketsQ其二,向该节点发v一ơ针对自己ID的节Ҏ询请求,从而通过节点I获取一pd与自p邻q的其他节点的信息;最后,h所有的k-bucketQ保证自己所获得的节点信息全部都是新鲜的?/p>