以太坊 p2p网络——kademlia协议(二)

协议消息

Kademlia协议包括四种远程RPC操作:PING、STORE、FIND_NODE、FIND_VALUE。

1、PING操作,探测一个节点,用以判断其是否仍然在线。

2、STORE操作,通知一个节点存储一个<key,value>对,以便以后查询需要。

3、FIND_NODE,本操作的接受者返回它所知道的更接近目标ID的K个节点的信息。这些节点的信息可以是从一个单独的K桶获得,也可以从多个K桶获得(如果最接近目标ID的K桶未满)。不管是哪种情况,接受者都将返回K个节点的信息给操作发起者。但如果接受者所有K桶的节点信息加起来也没有K个,则它会返回全部节点的信息给发起者。

4、FIND_VALUE操作,和FIND_NODE操作类似,不同的是它只需要返回一个节点的信息。如果本操作的接受者收到同一个key的STORE操作,则会直接返回存储的value值。

算法的三个参数:keyspace,k和α

    keyspace,即ID有多少位;决定每个节点的k桶大小;

    k, 每个一层k-bucket里装k个node的信息,即<node ID, IP Adress, port>;每次查找node时,返回k个node的信息; 对于某个特定的data,离其key最近的k个节点被会要求存储这个data。

    α,每次向其他node请求查找某个node时,会向α个node发出请求。

节点查询

    在节点查询的时候,从K桶中找出离所查询的键值最近的K个节点,然后向这K个节点发起FIND_NODE消息请求。消息接收者收到这些请求消息后将在他们的K桶中进行查询,找到某个条目和目标节点XOR为0,即已经寻址成功,则直接返回;如果没找到XOR结果为0的条目,则选取那个XOR值最小的条目对应的K桶中的K个条目返回给查询者。

     消息的请求者在收到响应后将使用它所收到的响应结果来更新它的结果列表,返回的结果也应该插入到刚才发起请求的那个K桶里,这个结果列表总是保持K个响应FIND_NODE消息请求的最优节点; 然后向这K个最优节点发起查询,因为刚开始的查询很可能K桶里存的不全是目标节点,而是潜在地离目标节点较近的节点;

     不断地迭代执行上述查询过程。因为每一个节点比其他节点对它周边的节点有更好的感知能力,因此响应结果将是一次一次离被搜索键值越来越近的某节点。如果本次响应结果中的节点没有比前次响应结果中的节点离被搜索键值更近了,这个查询迭代也就终止了;

     当这个迭代终止的时候,响应结果集中的K个最优节点就是整个网络中离被搜索键值最近的K个节点,从以上过程看,这显然是局部的,而非整个网络,因为这本质和最优解搜索算法一样,可能陷入局部最优解而无法获得全局最优解;节点信息中可以增加一个往返时间,或者叫做RTT的参数,这个参数可以被用来定义一个针对每个被查询节点的超时设置,即当向某个节点发起的查询超时的时候,另一个查询才会发起,当然,针对某个节点的查询在同一时刻从来不超过α个。

资源查询与存储

    当某个节点得到了新加入的数据(K/V),它会先计算自己与新数据的 key 之间的“距离”;然后再计算它所知道的其它节点与这个 key 的距离。如果计算下来,自己与 key 的距离最小,那么这个数据就保持在自己这里。否则的话,把这个数据转发给距离最小的节点。收到数据的另一个节点,也采用上述过程进行处理(递归处理)。

    当某个节点接收到查询数据的请求(key),它会先计算自己与 key 之间的“距离”;然后再计算它所知道的其它节点与这个 key 的距离。如果计算下来,自己与 key 的距离最小,那么就在自己这里找有没有 key 对应的 value。有的话就返回 value,没有的话就报错。否则的话,把这个数据转发给距离最小的节点。收到数据的另一个节点,也采用上述过程进行处理(递归处理)。

    考虑到节点未必都在线的情况,资源的值被存在多个节点上(节点中的K个),并且,为了提供冗余,还有可能在更多的节点上储存值;储存值的节点将定期搜索网络中与储存值所对应的键接近的K个节点并且把值复制到这些节点上,这些节点可作为那些下线的节点的补充 。

    对于那些普遍流行的内容,可能有更多的请求需求,通过让那些访问值的节点把值存储在附近的一些节点上来减少存储值的那些节点的负载,这种新的存储技术就是缓存技术,通过这种技术,依赖于请求的数量,资源的值被存储在离键越来越远的那些节点上(资源热度越高,缓存cache就越广泛),这使得那些流行的搜索可以更快地找到资源的储存者 。

    由于返回值的节点的NODE_ID远离值所对应的关键字,网络中的"热点"区域存在的可能性也降低了。依据与键的距离,缓存的那些节点在一段时间以后将会删除所存储的缓存值。DHT的某些实现(如Kad)即不提供冗余(复制)节点也不提供缓存,这主要是为了能够快速减少系统中的陈旧信息。在这种网络中,提供文件的那些节点将会周期性地更新网络上的信息(通过NODE_LOOKUP消息和STORE消息)。当存有某个文件的所有节点都下线了,关于该文件的相关的值(源和关键字)的更新也就停止了,该文件的相关信息也就从网络上完全消失了。

新节点加入

1、想要加入网络的节点首先要经历一个引导过程。在引导过程中,节点需要知道其他已加入该网络的某个节点的IP地址和端口号。随机生成一个散列值作为自己的 ID;

2. 新节点向它的唯一邻居(引导节点)发起NODE_LOOKUP操作请求来定位自己,这种"自我定位"将使得Kademlia的其他节点(收到请求的节点)能够使用新加入节点的ID填充他们的K桶。同时也能够使用那些查询过程的中间节点来填充新加入节点的K桶。这一自查询过程使得新加入节点自引导节点所在的那个K桶开始,由远及近,对沿途的所有节点逐步得到刷新,整条链路上的邻居都认识了这个新邻居

3. 最初的时候,节点仅有一个K桶(覆盖所有的ID范围),当有新节点需要插入该K桶时,如果K桶已满,K桶就开始分裂,分裂发生在节点的K桶的覆盖范围包含了该节点本身的ID的时候。对于节点内距离节点最近的那个K桶,Kademlia可以放松限制(即可以到达K时不发生分裂),因为桶内的所有节点离该节点距离最近,这些节点个数很可能超过K个,而且节点希望知道所有的这些最近的节点。因此,在路由树中,该节点附近很可能出现高度不平衡的二叉子树。假如K是20,新的节点可能有21个以上。这点保证使得该节点能够感知网络中附近区域的所有节点。