《第五节 ipfs网络拓扑算法---Kademlia》

p2p网络主要组件

email:[email protected] 作者:画笔

  • Kademlia网络协议
  • 内网穿透技术
  • 数据存储

Kademlia协议简介

Kademlia是一个二叉树算法,二叉树一个特点就是分层,分层的好处是数据流有方向,数据不怕物理距离有多长,而怕漫无目的的乱撞。所以有逻辑拓扑的网络就会非常快速。

逻辑运算:异或,是一个神奇的算法,加密解密用到它,我们这个也用到它。
这个是我的另外一篇博客,讲用异或实现对称加密的

Kademlia理论支撑

Kademlia使用异或距离算法来计算节点间的距离。

  • x ^ x = 0 //节点本机距离最近
  • x ^ y > 0 // 任意两个不同节点,必然有一定的距离
  • x ^ y = y ^ x // 任意两个节点,距离对方的距离都是等长的
  • x ^ y + y ^ z >= x ^ z //三角形三边关系,用最少的节点转发是距离最短的
  • x + y >= x ^ y //两个自然数相加,如果bit没有溢出则保留Bit,溢出会导致进位。而^是没有进位的
  • x ^ y ^ y = x // 取反两次等于原值
  • (x ^ y) ^ (y ^ z) = x ^ z // 由于^是没有溢出的,所以 ^后数据是有保留的。只不过换了一种存储方式。你要看原始数据,需要还原一下,它一直在那里。

所说的距离是逻辑上的距离,与地理位置无关,所以有可能两个节点之间计算得到的逻辑距离很近,但实际上地理上的距离却很远 。

为简化期间,我们用一颗小的二叉树来举例:

0
1
0
1
0
1
0
1
0
1
0
1
0
1
node0
node1
node2
node3
node4
node5
node6
000
001
010
011
100
101
110
111

(图1)

二叉树节点编号用3个bit,bit[2:0],最底层是8个节点。节点编号从左往右是:000、001、010、011、100、101、110、111

因为二叉树叶子节点是有顺序的,二级制的一个bit对应一个层,看前四个节点bit2都为0(左),后四个节点bit2都为1(右),如此分叉,直到最底层。

从节点001往外看整个网络,看到有三个子树,如图(2)所示:

(图2)

注意观察:每个子树的前几个bit是一致的,也就每个子树内节点之间的距离是最近的,也就是每个子树里面的任意一个节点,都可以作为代理节点(提供转发服务),以最近的距离将数据转发到所在子树内。
这样,节点001,遍历整个网络,只需要记住(保存在内存或磁盘)三个子树里面的某些节点的通讯ip个port,就可以跟整个网络进行通讯。
又由于2的幂次方,当bit不是3,而是256时,leaf叶子数量达到232个,最大的一颗子树的叶片数量为:232 - 2^31个,所以001不可能用内存记录全部子树的通讯地址,因此001节点只需要记住一个子里面任意8个节点的通讯地址。对于256bit的id,最大32层转发,256颗子树

横看成林岭,侧成峰,远近高低各不同。不识庐山真面目,只缘身在此山中。什么是逻辑,什么是哲学?自然就是,看到的一切皆为哲学。

简化路由表

每个叶子节点都按照自己的视图构建一个路由表(存储其它子树的id和通讯地址),简化起见还是以上面的二叉树为例:
bucket3、bit[2:3]:100–192.168.31.1:10001、101–192.168.31.211:8900、110–192.168.31.176:9000、111–192.168.31.79:8000
bucket2、bit[1:2]:010–192.168.31.160:9999、011–192.168.31.132:36104
bucket1、bit[0:1]:000–192.168.31.199:21852
bucket0、bit[0:0]:001–192.168.31.159:9521

在二叉树比较矮的情况下,由于001节点存储了全部的节点的通讯地址,
001现在需要与id为001的节点通讯,001^001=000,就到bucket0去查找列表,然后去127.0.0.1:9521地址,去跟daemon通讯。
001现在需要与id为000的节点通讯,001^000=001,就到bucket1去查找列表,然后去92.168.31.199:21852地址,去跟daemon通讯。
001现在需要与id为011的节点通讯,001^011=010,就到bucket3去查找列表,然后去192.168.31.132:36104地址,去跟daemon通讯。
001现在需要与id为101的节点通讯,001^101=100,就到bucket3去查找列表,然后去192.168.31.211:8900地址,去跟daemon通讯。

其中bucketx,里面的x就是子树的编号。

真实的路由表

上面为了举例告诉你,路由表是如何设计的。生产环境中,节点是分散在不同局域网数量非常多的,可能有几千万个节点(迅雷),那么bucket255,上面的节点就占据了整个节点的一半,如果001全部存储节点id,需要的内存特别大,并且索引的适合也比较花时间(bucket之间是计算,bucket内部是链表遍历)。
现在我规定:每个bucket上面最多存8个节点的id,通过节点路由的方式来遍历每个节点。 这样离我比较近的节点存的百分百就会比较多,离我比较远的节点就会存的比较少。分子为8,分母越大(距离越远),百分比越小。ip不再是局域网ip,而是nat 公网ip和转发端口
每个节点都有自己角度的路由表,由于节点数量众多,一次命不中,因此需要多此转发,才能最终找到目标节点。

路由表更新

二叉树是有root节点的,是整颗树的根。
公网节点,作为启动节点,负责构建和维护整个p2p网络。

  • 路由表获取,通过
{"cmd-type":"find_node","src-id":"001","addr":"80.91.95.211:7000","online_stamp":"2019.9.6:12:00:00","upsrteam-bandwidth":"10M","downsrteam-bandwidth":"10M"}

,通过其它节点转发,某个子树里面的若干节点会将实时的在线节点打包成json格式原路返回给发起节点

  • 路由表被动更新,当某个节点收到一个节点的ping或查询时,对方节点必然是在线的。
  • 检测在线节点是否离线,对于某颗子树,里面的至多8个节点进行ping,不在线则用路由表获取得到的值替补离线的节点,以保持网络的连通性。
  • 替补原则:如果一颗子树里面的入口节点,没有死掉的话,就不会主动踢掉它。可以在一定程度上防止ddos攻击。

资源存储获取

资源io有两种方式:

  • 1、资源哈希映射到Kademlia某个节点,缺陷是不灵活,容易在节点掉线后出现资源找不到的情况,也难以实现资源的冗余机制。好处是可以直接通过哈希计算,找到目标节点,不需要广播给全网去索引资源。
  • 2、资源存储在与任意节点,请求全网广播,全网有资源的节点在索引的本地目录有目标资源后,回包给目标节点。缺陷是广播数据流非常巨大,节点越多,数据流越大,虽然有拓扑保证收敛边界为logN,但是节点数量太多,基数太大,限制了总的节点数量不能超过一定值(木桶效应,最弱的节点可能频繁挂机,当挂机数量到一定程度,就会影响主网,形成雪崩效应)。 ipfs目前是这种设计方式。

备注:流控是转发的(请求定位,p2p内网穿透)、数据流是走的p2p,不然数据流那么大,不管1还是2都会扛不住。

关于ID的生成规则

  • 确定性
  • 唯一性
  • 随机性

我在这里推荐一个算法:base58(sha256(rsa_gen_pub_key())),其中rsa非对称加密算法满足了随机性,sha256满足了唯一性,base58是可视化编码。 生成数据串作为id,而不是ip或macaddr,满足了确定性。

发布了61 篇原创文章 · 获赞 63 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/jacky128256/article/details/100575080