我在专门介绍我的金融仪表盘副业项目的出版物中已经提到,对我来说,学习新东西最有效的方式是着手一个“宠儿项目”,这个项目足够激励我,不会半途而废。我强迫自己使用我想学习的新技术来开发项目,所以在这个过程中,我最终得到了一个全新的副项目和一大堆新技能。金融仪表板是学习GraphQL和ReactJS的借口(老实说,从那以后我越来越积极地使用ReactJS,所以它得到了回报)。今天,我将分享我的“借口”来提高我在libp2p和围棋并发编程方面的技能(使用Goroutine、等待组、锁等):一个内置在libp2p中的IPFS爬行器。
到目前为止,我对P2P、分布式技术以及它们未来(而不是将来)对互联网和整个社会架构的影响已经不是什么秘密了。有一天,我在思考如何部署P2P网络来在生产中协调数据中心联盟中的所有设备,这时我意识到:数据中心基础设施受到高度监控,以便检测与系统基准操作的任何最小偏差。在传统(和集中式)基础设施中,分析是“简单的”,您可以使用中央监控系统从您的基础设施中收集每个指标,因为您是基础设施的所有者和管理者。但是,当您拥有一个由多个实体拥有的分散系统,并且您只运行网络的一小部分时,如何监视其整体状态并确保系统正常工作呢?您只保留网络的本地视图,并且希望了解整个网络的状态。在分布式、非结构化和自组织网络中进行监控和分析是很困难的,特别是在没有集中式基础设施来协调一切的情况下。因此,以深入研究并发性和libp2p(并由一些外部环境触发)为借口,我决定开始小规模地研究这个问题。
我选择从小事做起,但雄心勃勃。我想在一个真实的网络中探索这个问题,所以在我的本地机器上建立一个8节点的网络来进行实验就是作弊。因此,我决定尝试从最大的P2P网络之一IPFS收集一些指标。具体地说,我打算尝试监视网络中活动的节点总数,以及它的每日流失率(全天离开的节点数的比率)。
对这个问题的第一次搜索显示了学术论文试图绘制IPFS网络的几个结果,以及libp2p网络爬虫的现有实现。所以以前已经有人研究过这个问题了。现在轮到我看我是否能追随他们的脚步了。
前言写得够多了,我拿起笔记本上的一张白页,开始为我的工具涂鸦一个潜在的设计,最后我为我的爬虫程序得出了以下架构:
爬虫程序将由三个不同的进程构成:爬虫程序进程(负责随机遍历分布式哈希表以搜索连接的节点-稍后将详细介绍这一点);活动性进程(负责检查节点是否已经看到还活着);以及报告进程(输出分析的当前状态,没什么特别的)。
为了爬行网络,我选择了一个相当无结构的方案,路由分布式哈希表的随机行走。爬虫程序会随机选择一个节点ID,并询问网络“嘿,伙计们!给我一份离这个人最近的x个节点的列表“。然后,爬行器将尝试与这些节点建立临时连接,以查看它们是否处于运行状态,以及它们是否正在将它们添加到网络中的活动节点列表中。通过这种方式,我们“随机遍历”网络以搜索活动节点。我也许可以使用更结构化的搜索,其中不是随机选择新的节点ID,而是从我的k-bucket中取出所有对等点,查看它们是否还活着,递归地请求从k-bucket到这些节点的对等点列表,依此类推。我的第一个实现遵循这个方案。它可能最终会更准确,但感觉比随机方法慢。
随机游走搜索网络中的活动节点,但我需要一种方法来检查这些节点是否仍然活着,或者它们是否已经离开网络。这就是活泼过程的目的。此过程非常简单,当Crawler搜索并更新活动节点列表时,此过程会循环活动节点列表,并尝试与它们建立临时连接,以查看它们是否仍处于活动状态。
最后,报告流程只获取存储的指标,并定期显示它们。最初,我从看到的节点收集的唯一指标是:最后一次看到它的时刻,以及它是否在NAT之后。最后一个度量对于检查节点是否仍然活着非常重要,因为即使临时连接失败,也可能是因为他在NAT之后,而不是因为他退出了。这就是为什么我们需要事先知道节点在NAT之后,以便知道如何识别离开节点。
您可以在以下存储库中找到该工具的代码:https://github.com/adlrocha/go-libp2p-crawler.。自述文件包括一些关于设计决策和实现的信息,但是如果您只是好奇地想看看工具如何工作,您可以克隆repo,编译代码并运行它,看看神奇的事情发生了:
Crawler和-livitivity标志允许您分别选择专用于搜索新节点和检查节点是否仍处于活动状态的goroutines数量。
如果您想更多地了解我是如何实现此工具的,让我带您了解一下它的实现:
系统在启动时做的第一件事是为每个想要启动的爬行器进程启动一个新的libp2p节点。然后,这些节点连接到IPFS引导节点。当一个节点被引导后,它开始搜索新的节点。
节点只能运行Crawler进程,或者它可以根据选择用于执行的Crawler数量和活动性同时运行Crawler和活动性进程(请记住,Crawler节点的设计使它们不能仅执行活动性检查,因此您永远不能拥有比Crawler工作器更多的活跃性工作器。这是一个设计选择,可以很容易地修改)。
来存储有关看到的节点、网络中的活动节点等的数据。我选择使用LevelDB。该系统在同一数据存储中有多个写入的Goroutine,因此使用LevelDB是避免数据争用的一种非常简单的方法。在一些初始测试之后,我还选择使用锁,以便显式删除任何潜在的数据竞争。
报告过程仅显示有关今天看到和离开的节点数、网络中的活动节点数(至少是我们对其的看法)和每日流失量的信息。我还存储了有关节点最后一次出现的时间以及他是否在NAT之后的信息,这样我还可以计算NAT之后的节点率,或粗略估计节点保持连接的平均时间。我还考虑过探索其他指标,比如网络其余部分的平均RTT、Seed节点中的可用传输,以及关于网络的其他有趣指标,但我选择暂时专注于简单的指标(爬行非常困难,值得一试)。
在长时间执行该工具之后,我开始体验离开网络的总节点数在达到约500个时是如何重新启动的。这个错误真的困扰着我,我独立地对计数器和所有的Goroutine进行了几个独立的测试,直到我选择了一个没有LevelDB的替代实现(您可以在repo的分支功能/noLevelDB中找到它)。有了这个替代实现,问题就解决了,您知道发生了什么吗?我用来存储数据的结构,以及在活跃性过程中我必须使用迭代器循环遍历数据的事实扰乱了同步。我选择使用LevelDB的另一个原因是为了持久化看到的节点列表,但是由于这个bug,并且考虑到我可以使用其他不那么复杂的方案来持久化数据,所以我没有投入更多的时间来尝试修复这篇文章的bug(尽管这是我在找到时间时一定会做的事情)。
老实说,我一生都在玩这个项目。我在这份时事通讯中已经说过几次了,但让我再说一次,我希望我能把它作为我的全职工作!如果你测试这个工具,我很想知道你的反馈。根据您的反馈,扩展项目并积极维护它可能是有意义的。在此期间,在深入研究libp2p之后,我又想出了一些要探索的想法,并且我已经开始了一个新的“快速而肮脏的项目”来学习新的东西。下一个结合了WASM、libp2p和Rust。敬请关注结果(根据我未来几周的时间,我可能会在一个月左右公布结果)。回头见!