在弄清IPFS的kademlia DHT之前,首先需要了解它的k桶原理,IPFS的kbucket路由表主要包含一个桶的大小(KValue = 20),一组k桶数组,其中每个k桶里面存放的是libp2p的节点id的双向链表。该路由表主要利用XOR算法计算与主机节点之间的逻辑距离,并分别将这些节点存放到不同的k桶中。IPFS默认k桶大小为20,libp2p节点peer.ID最大256位,理论上两个不同节点之间距离最大值是255,该路由表最多可以存放5120个libp2p节点。
基本数据结构
type RoutingTable struct {
// ID of the local peer
local ID// kBuckets define all the fingers to other nodes.
Buckets []*Bucket
bucketsize int
}
type Bucket struct {
lk sync.RWMutex
list *list.List
}
基本算法
1. XOR
libp2p节点id长度256位,由32个字节组成,XOR算法将节点id从低到高进行与操作。简单点讲两个位数相同则为0,不同则为1。
2. 节点距离 ZeroPrefixLen
两个节点之间的距离首先通过XOR算法计算出两个节点id之间的值,然后再从低到高位计算该值第一个非0的长度
任意一个节点计算自己节点id之间的距离必然是256,同理,如果有两个节点,一个节点第一个字节为0,一个节点第一个字节为1,那么对任意节点,这两个节点之中必然有一个节点与该任意节点之间的距离为0。
3. 比较Less
该比较算法直接把节点ID的32个字节从低到高比较字节之间的大小。
该算法主要用于在路由表中查找到节点数组后,再与自己的节点算出一个距离后,再进行排序。
主要接口
1. Update(p peer.ID)
1) 首先以目标节点id与自己节点id之间的距离,然后通过该距离找到对应的k桶,并将该节点id放入到该k桶最前面。
2) 如果该k桶容量超过了最大容量,那么
2.1) 如果不是最后一个桶,那么移除当前桶的最后一个节点id
2.2) 依次拆分最后一个桶,如果当前桶内节点id与主节点id距离为5的话,那么所有距离大于5的节点id都拆分到后面一个桶里
2. NearestPeers(id ID, count int)
1) 根据id计算出与主节点id之间的距离,然后找到对应的k桶,然后从该k桶里面的所有节点id拷贝出来
2) 如果该拷贝出来的数组容量低于count,则从该k桶前后两个k桶里面拷贝对应的节点id
3) 对拷贝出来的节点进行排序,并返回最终的数组
小结
IPFS的模块化做得非常好,针对每一个模块,首先关注的是其测试用例,里面基本上能覆盖该模块所有主要路径。运行go test或者用dlv进行调试相关的内部逻辑。
并不是所有的的节点都能够加入到本地的路由表中,如下面实例
func TestTableFindMultiple(t *testing.T) {
local := tu.RandPeerIDFatal(t)
m := pstore.NewMetrics()
rt := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m)peers := make([]peer.ID, 51200)
for i := 0; i < 51200; i++ {
peers[i] = tu.RandPeerIDFatal(t)
rt.Update(peers[i])
}
rt.Print()t.Logf("totoal size is: %d", rt.Size())
}
最终返回的结果也就是241,很多前面的节点都会被淘汰掉