Kademlia: 基于异或运算的P2P信息系统

来源:互联网 发布:python怎么写爬虫 编辑:程序博客网 时间:2024/04/19 22:23
Petar Maymounkov and David Mazi`eres
fpetar,dmg@cs.nyu.edu
http://kademlia.scs.cs.nyu.edu
 
摘要
本文我们将描述一个在容易出错的网络环境中拥有可证实的稳定性和高性能稳定性的点对点(P2P)系统。我们的系统使用一个很新颖的基于异或运算的拓扑来发送查询并且定位节点, 这简化了算法并且使验证更加容易。 这种拓扑结构具有以下特性,它能通过交换消息传达和加强节点间的有用联系信息。本系统利用这个信息来发送平行的,异步的查询消息来对付节点的失效而不会给用户带来超时时延。
 
1.介绍
    本论文描述Kademlia , 一个点对点(P2P)的<键, 值>元组存储和查询系统。 Kademlia拥有许多的可喜的特点,这些特点是任何以前的P2P系统所无法同时提供的。它减少了节点必须发送的用来相互认识的配置消息的数量。在做键查询的同时, 配置消息将会被自动传播。 节点拥有足够的知识和灵活性来通过低时延路径发送查询请求。 Kademlia使用平行的,异步的查询请求来避免节点失效所带来的超时时延。通过节点记录相互的存在的算法可以抵抗某些基本的拒绝服务(DoS)攻击。 最后, 仅仅使用在分布式运行时间上较弱的假设(通过对现有点对点系统的测量而确认的这些假设),我们可以正式的证实Kademlia的许多重要特性
    Kademlia 使用了许多点对点(P2P)系统的基本方法。 键是一个160-bit的隐式数量(例如, 对一些大型数据进行SHA-1哈希的值)。 每个参与的机器都拥有一个节点ID, 160位的键。 <键, 值>对将存储在那些ID与键很‘接近’的节点上, 这里‘接近’当然是按照一个接近度的概念来计算的。最后, 一个基于节点ID的路由算法使得任何人可以在一个目的键附近定位到一个服务器。
Kademlia的许多的优点都是得益于它使用了一个很新颖的方法, 那就是用节点间的键作异或运算的结果来作为节点间的距离。异或运算是对称的, 允许Kademlia的参与者接收来自相同分布的并且包含在其路由表中的节点的查找请求。如果没有这个性质,就像Chord一样,系统无法从它们收到的查询请求中学习到有用的路由信息。更糟的是, 由于Chord中的运算是不对称的, Chord的路由表更加严格。 Chord节点的查找表的每一项都必须存储精确的按ID域的间隔递增的节点。在这个间隔内的任何节点都比这个间隔内的某些键大,因此离键很远。相反,Kademlia 可以在一定的间隔内发送请求给任何节点, 允许基于时延来选择路由,甚至发送平行的,异步的查询。
为了在特定的ID附近定位节点,Kademlia自始至终使用一个单程的路由算法。相反,其它一些系统使用一种算法来接近目标ID,然后在最后的几个跳数使用另外一种算法。在现有系统中,Kademlia与pastry的第一阶段最像,(虽然作者并没有用这种方式来描述),Kademlia 的异或运算可以使当前节点到目标ID的距离粗略的持续减半,以此来寻找节点。在第二阶段,Pastry不再使用距离运算,而是改为比较ID的数字区别。它使用第二种,数字区别运算作为替代。不幸的是,按第二种运算计算的接近比第一种的远得多,这造成特定节点ID值的中断,降低了性能,并且导致在最差行为下的正式分析的尝试失败。
 
2.系统描述
每个Kademlia节点有一个160位的节点ID。在Chord系统中,ID是通过某种规则构造出来的,但在这片文章中,为了简化,我们假设每台机器在加入系统时将选择一个随机的160位值。每条节点发送的消息包含它的节点ID, 同时允许接收者记录下发送者的存在信息,如果有必要的话。
键,同样也是160位的标识符。为了发布和寻找<键,值>对,Kademlia依赖一个概念,那就是两标识符之间的距离的概念。给定两个标识符, x和y, Kademlia定义两者的位异或(XOR)的结果作为两者的距离,d(x,y)=x⊕y。我们首先注意到异或运算是一个有意义的运算,虽然不是欧几里得运算。很明显具有下面的性质: d(x,x)=0;如果x≠y, 则d(x, y)>0;任意的x, y: d(x, y) = d(y, x)。 异或运算还满足三角性质:d(x, y) + d(y, z) ≥ d(x, z)。 这个三角性质之所以成立是基于下面这个事实: d(x, z) = d(x, y) + d(y, z); 并且任意的a>=0, b≥0: a+b≥a⊕b。
跟Chord的顺时针循环运算一样,异或运算也是单向的。对于给定的一个点x以及距离Δ,仅有一个点y,使得d(x, y) = Δ。 单向性确保所有对于相同的键的查询将汇聚到相同路径中来,而不管是什么起源节点。因此,在查找路径上缓存<键,值>对可以减少‘撞车’的机会。跟Pastry而不是Chord一样, 异或运算也是对称的。(对所有的x以及y, d(x,y) = d(y,x))
 
2.1.节点状态
Kademlia节点存储互相的联系信息,以用于路由查询消息。对于任何0 =< i < 160, 每个节点保存那些到本节点的距离为2i 到2i+1之间的节点信息列表,包括<IP地址,UDP端口, 节点ID>。我们把这些列表称为K-桶。每个K-桶中的节点按最后联系的时间排序――最久未联系的节点放在头部,最近联系的节点放在尾部。对于比较小的i值,K-桶通常是空的(因为没有合适的节点存在于系统中)。对于比较大的i值,列表节点数可以达到k的大小,k是一个系统级别的冗余参数。k值的选择必须满足一个条件,那就是任意k个节点在一个小时内都失效的可能性很小(例如k =20)。
图1:以当前已在线时间的函数的形式显示了节点在接下来的一小时后继续在线的比例。X轴代表分钟,y轴代表那些已经在线了x分钟的节点中将继续在线1小时的比例。
 
当一个Kademlia节点收到来自另外一个节点的任何消息(请求的或者回复的),它将更新自己的一个K-桶,即发送节点ID对应的那个桶。如果发送节点已经存在于接收者的K-桶中,接收者会把它移到列表的尾部。如果这个节点还没有存在于对应的K-桶中并且这个桶少于k个节点,则接收者把发送者插入到列表的尾部。如果对应的K-桶已经满了,则发送者将向该K-桶中的最久未联系节点发送ping命令测试是否存在,如果最久未联系节点没有回复,则把它从列表中移除,并把新的发送者插入到列表尾部。如果它回复了,则新的发送者信息会丢弃掉。
K-桶非常高效的实现了剔除最久未联系节点的策略,存活的节点将永远不会从列表中移除。这种偏向保留旧节点的做法是我们对由Saroiu等人收集的Gnutella协议的跟踪数据进行分析而得出来的。图1以当前已存在时间的函数的形式显示了Gnutella节点在一小时后继续在线的比例。一个节点存活的时间越长,则这个节点继续存活一小时的可能性越大。通过保留存活时间最长的那些节点,K-桶中存储的节点继续在线的概率大大提高了。
K-桶的第二个优点是它提供了对一定的拒绝服务(DoS)的攻击的抵抗。系统中不断涌入新节点并不会造成节点路由状态的更新过快。Kademlia节点只有在旧节点离开系统时才会向k-桶中插入新节点。
 
2.2.Kademlia协议
Kademlia协议由4个远程过程调用(RPC)组成:PING,STORE,FIND_NODE, FIND_VALUE。 PING RPC 测试节点是否存在。STORE指示一个节点存储一个<键,值>对以用于以后的检索。
FIND_NODE 把160位ID作为变量,RPC的接收者将返回k个它所知道的最接近目标ID的<IP地址,UDP端口,节点ID>元组。这些元组可以来自于一个K-桶,也可以来自于多个K-桶(当最接近的K-桶没有满时)。在任何情况下, RPC接收者都必须返回k项(除非这个节点的所有的K-桶的元组加起来都少于k个,这种情况下RPC接收者返回所有它知道的节点)
FIND_VALUE和FIND_NODE行为相似――返回<IP地址,UDP端口,节点ID>元组。仅有一点是不同的,如果RPC接收者已经收到了这个键的STORE RPC,则只需要返回这个已存储的值。
在所有RPC中,接收者都必须回应一个160位的随机RPC ID,这可以防止地址伪造。PING中则可以为RPC接收者在RPC回复中捎回以对发送者的网络地址获得额外的保证。
Kademlia参与者必须做的最重要的工作是为一个给定的节点ID定位k个最接近节点。我们称这个过程为节点查询。Kademlia使用一种递归算法来做节点查询。查询的发起者从最接近的非空的K-桶中取出а个节点(或者,如果这个桶没有а项,则只取出它所知道的最接近的几个节点)。发起者然后向选定的а个节点发送平行的,异步的FIND_NODE RPC。а是一个系统级别的并行参数,比如为3。
在这个递归的步骤中,发起者重新发送FIND_NODE给那些从上次RPC中学习到的节点(这个递归可以在之前的所有的а个RPC返回之前开始)。在这返回的与目标最接近的k个节点中,发起者将选择а个还没有被询问过的节点并且重新发送FIND_NODE RPC给它们。没有立即作出响应的节点将不再予以考虑除非并且直到它们作出响应。如果经过一轮的FIND_NODE都没有返回一个比已知最接近的节点更接近的节点,则发起者将重新向所有k个未曾询问的最接近节点发送FIND_NODE。直到发起者已经询问了k个最接近节点并且得到了响应,这个查询才结束。当а=1时,查询算法在消息开支和检测失效节点时的时延上与Chord非常相似。 然而,Kademlia可以做到低时延路由因为它有足够的灵活性来选择k个节点中的一个去做查询。
按照上面的查询过程,大多数的操作都可以实现。要存储一个<键,值>对,参与者定位k个与键最接近的节点然后向这些节点发送STORE RPC。另外,每个节点每个小时都会重新发布它所有的<键,值>对。这可以以高概率的把握确保<键,值>对的持续存在于系统中(我们将会在验证概略一节中看到)。通常来说,我们还要求<键,值>对的原始发布者每隔24小时重新发布一次。否则,所有的<键,值>对在最原始发布的24小时后失效,以尽量减少系统中的陈旧信息。
最后,为了维持<键,值>对在发布-搜索生命周期中的一致性,我们要求任何时候节点w拥有一个新节点u,u比w更接近w中的一些<键,值>对。w将复制这些<键,值>对给u并且不从自己的数据库中删除。
为了查找到一个<键,值>对,节点首先查找k个ID与键接近的节点。然而,值查询使用FIND_VALUE而不是FIND_NODE RPC。 而且,只要任何节点返回了值,则这个过程立即结束。为了缓存(caching)的缘故,只要一个查询成功了,这个请求节点将会把这个<键,值>对存储到它拥有的最接近的并且没能返回值的节点上。
由于这个拓扑的单向性,对相同的键的以后的搜索将很有可能在查询最接近节点前命中已缓存的项。对于一个特定的键,经过多次的查找和传播,系统可能在许多的节点上都缓存了这个键。为了避免“过度缓存”,我们设计了一个<键,值>对在任何节点的数据库中的存活时间与当前节点和与键ID最接近的节点ID之间的节点数成指数级的反比例关系。简单的剔除最久未联系节点会导致相似的生存时间分布,没有很自然的方法来选择缓存大小,因为节点不能提前知道系统将会存储多少个值。
    一般来说,由于存在于节点之间的查询的通信,桶会保持不停地刷新。为了避免当没有通信时的病态情况,每个节点对在一个小时内没有做过节点查询的桶进行刷新,刷新意味着在桶的范围内选择一个随机ID然后为这个ID做节点搜索。
为了加入到这个网络中,节点u必须与一个已经加入到网络中的节点w联系。u把w加入到合适的桶中,然后u为自己的节点ID做一次节点查找。最后,节点u刷新所有比最接近的邻居节点更远的K-桶。在这个刷新过程中,节点u进行了两项必需的工作:既填充了自己的K-桶,又把自己插入到了其它节点的K-桶中。
 
3.验证概述
为了验证我们系统中的特有的函数,我们必须证实绝大多数的操作花费[log n]+ c的时间开销,并且c是一个比较小的常数,并且<键,值>查找将会以很高的概率返回一个存储在系统中的键。
我们首先做一些定义。对于一个覆盖距离的范围为[2i, 2i+1)K-桶,定义这个桶的索引号为i。定义节点的深度h为160-i,其中i是最小的非空的桶的索引号。定义在节点x中节点y的桶高度为y将插入到x的桶的索引号减去x的最不重要的空桶的索引号。由于节点ID是随机选择的,因此高度的不统一分布是不太可能的。因此,在非常高的概率下,任意一个给定节点的高度在log n之内,其中n是系统中的节点数。而且,对于一个ID,最接近节点在第k接近的节点中的桶高度很有可能是在常数log k之内。
下一步我们将假设一个不变的条件,那就是每个节点的每个K-桶包含至少一个节点的联系信息,如果这个节点存在于一个合适的范围中。有了这个假设,我们可以发现节点的查找过程是正确的并且时间开销是指数级的。假设与目标ID最接近的节点的深度是h。如果这个节点的h个最有意义的K-桶都是非空的,查询过程在每一步都可以查找到一个到目标节点的距离更接近一半的节点(或者说距离更近了一个bit),因此在h -log k步后目标节点将会出现。如果这个节点的一个K-桶是空的,可能是这样的一种情况,目标节点恰好在空桶对应的距离范围之内。这种情况下,最后的几步并不能使距离减半。然而,搜索还是能正确的继续下去就像键中与空桶相关的那个位已经被置反了。因此,查找算法总是能在h -log k步后返回最接近节点。而且,一旦最接近节点已经找到,并行度会从а扩展到k。寻找到剩下的k-1个最接近节点的步数将不会超过最接近节点在第k接近节点中的桶高度,即不太可能超过log k加上一个常数。
为了证实前面的不变条件的正确性,首先考虑桶刷新的效果,如果不变条件成立。在被刷新后,一个桶或者包含k个有效节点,或者包含在它范围内的所有节点,如果少于k个节点存在的话(这是从节点的查找过程的正确性而得出来的。)新加入的节点也会被插入到任何没有满的桶中去。因此,唯一违反这个不变条件的方法就是在一个特别的桶的范围内存在k+1个活更多的节点,并且桶中的k个节点在没有查找或刷新的干涉下全部失效。然而,k值被精确的选择以保证使所有节点在一小时内(最大的刷新时间)全都失效的概率足够小。
实际上,失败的概率比k个节点在1小时内全都离开的概率小得多,因为每个进入或外出的请求消息都会更新节点的桶。这是异或运算的对称性产生的,因为在一次进入或外出的请求中,与一个给定节点通信的对端节点的ID在该节点的桶范围之内的分布是非常均匀的。
而且,即使这个不变条件在单个节点的单个桶中的确失效了,这也只影响到运行时间(在某些查询中添加一个跳数),并不会影响到节点查找的正确性。只有在查找路径中的k个节点都必须在没有查找或刷新的干涉下在相同的桶中丢失k个节点,才可能造成一次查找失败。如果不同的节点的桶没有重叠,这种情况发生的概率是2-k*k。否则,节点出现在多个其它的节点的桶中,这就很可能会有更长的运行时间和更低概率的失败情况。
现在我们来考虑下<键,值>对的恢复问题。当一个<键,值>对发布时,它将在k个与键接近的节点中存储。同时每隔一小时将重新发布一次。因为即使是新节点(最不可靠的节点)都有1/2的概率持续存活一个小时,一个小时后<键,值>对仍然存在于k个最接近节点中的一个上的概率是1-2-k这个性质并不会由于有接近键的新节点的插入而改变,因为一旦有这样的节点插入,它们为了填充它们的桶将会与他们的最接近的那些节点交互,从而收到附近的它们应该存储的<键,值>对。当然,如果这k个最接近键的节点都失效了,并且这个<键,值>对没有在其它任何地方缓存,Kademlia将会丢失这个<键,值>对。
 
4.讨论
我们使用的基于异或拓扑的路由算法与Pastry [1], Tapestry [2]的路由算法中的第一步和 Plaxton的分布式搜索算法都非常的相似。然而,所有的这三个算法,当他们选择一次接近目标节点b个bit的时候都会产生问题(为了加速的目的)。如果没有异或拓扑,我们还需要一个额外的算法结构来从与目标节点拥有相同的前缀但是接下来的b个bit的数字不同的节点找到目标节点。所有的这三个算法在解决这个问题上采取的方法都是各不相同的,每个都有其不足之处;它们在大小为O(2b log2b n)的主表之外另外需要一个大小为O(2b)次要路由表,这增加了自举和维护的开支,使协议变的更加复杂了,而且对于Pastry和Tapestry来说阻止了正确性与一致性的正式分析。Plaxton虽然可以得到证实,但在像点对点(P2P)网络中的极易失效的环境中不太适应。
相反,Kademlia则非常容易的以不是2的基数被优化。我们可以配置我们的桶表来使每一跳b个bit的速度来接近目标节点。这就要求满足一个条件,那就是任意的0 < j < 2b0 i < 160/b在与我们的距离为[j2160-(i+1)b, (j+1)2160-(i+1)b]的范围内就要有一个桶,这个有实际的项的总量预计不会超过个桶。目前的实现中我们令b=5。
 
5.总结
使用了新颖的基于异或运算的拓扑,Kademlia是第一个结合了可证实的一致性和高性能,最小时延路由,和一个对称,单向的拓扑的点对点(P2P)系统。此外,Kademlia引入了一个并发参数,а,这让人们可以通过调整带宽的一个常数参数来进行异步最低时延的跳选择和不产生时延的失效恢复。最后,Kademlia是第一个利用了节点失效与它的已运行时间成反比这个事实的点对点(P2P)系统。
 
参考文献
[1] A. Rowstron and P. Druschel. Pastry: Scalable, distributed object location and routing for large-scale peer-to-peer systems. Accepted for Middleware, 2001, 2001. http://research.microsoft.com/˜antr/pastry/.
 
[2] Ben Y. Zhao, John Kubiatowicz, and Anthony Joseph. Tapestry: an infrastructure for fault-tolerant wide-area location and routing. Technical Report UCB/CSD-01-1141, U.C. Berkeley, April 2001.
 
[3] Andr´ea W. Richa C. Greg Plaxton, Rajmohan Rajaraman. Accessing nearby copies of replicated objects in a distributed environment. In Proceedings of the ACM SPAA, pages 311–320, June 1997.
 
[4] Stefan Saroiu, P. Krishna Gummadi and Steven D. Gribble. A Measurement Study of Peer-to-Peer File Sharing Systems. Technical Report UW-CSE-01-06-02, University of Washington, Department of Computer Science and Engineering, July 2001.
 
[5] Ion Stoica, Robert Morris, David Karger, M. Frans Kaashoek, and Hari Balakrishnan. Chord: A scalable peer-to-peer lookup service for internet applications. In Proceedings of the ACM SIGCOMM ’01 Conference, San Diego, California, August 2001.