< 返回版块

2019-03-21 12:19    责任编辑:Mike

本文转载自:https://www.driftluo.com/admin/article/view?id=da927b75-47b6-4fcd-8289-4a373e46331c

P2P 网络系列(一)

自学习编程以来,一直与网络编程缘分不浅,从最初的 Python 小爬虫项目,Django Web 项目,到 Rust 的 Influxdb client 项目,blog 项目,再到最近的 cita-cli 项目。以上项目都是基于 http 协议构建,从七层/四层网络协议模型上来看,http 属于应用层协议,它架设在 TCP 协议之上,一次 http 通信是一次很短的无状态连接,每次通信结束就直接断开连接,而 TCP 连接属于长连接,只要我们在使用 TCP 连接时不主动断开,那么它的连接会一直存在。

本以为作为菜鸟的我,了解网络协议到特性级别就够使用了,毕竟不是每个人都需要对协议中每个字段的长度、意义等都了然于胸,才能正确得写出基于协议之上的应用程序。在 influxdb client 项目中接触到了解析文本协议的一点东西,可以看成是基于 http 之上的自定义文本协议,到这,稍微了解了一点点关于协议具体内容的案例,直到去年,在参与 CITA 项目的过程中,接触到 Bitcoin 的 peer to peer 网络协议,才慢慢了解到作为互联网时代的网络协议,下面的玄机如此之深,然而,天真如我还是低估了里面的门道,只能说,如果不自己亲手完全实现一遍,永远无法知道里面的小坑到底有多少。

为什么要重写网络库

从去年 10 月开始,因为工作原因,开始接触 libp2p 协议,花了点时间看代码,发现由于代码是顺着概念实现的,而概念太多导致看不懂,又回过头去把 libp2p spec 刷了一遍,了解了 swarm、mutiladdr 等概念之后,回过头来继续看代码,大致能理解了,随后开始尝试使用该库(rust-libp2p),由于当时的库中缺少文档和示例,摸索与提问并行之下,好不容易搭建了一个 demo 可以跑起来通信了,正当我欢天喜地得将 demo 封装成库,移植到 CITA 中时,碰到了一些神奇的问题,在尝试自己解决失败,求助也失败的情况下,与组内多人讨论了不下一月的时间,议题就一个:“我们是否需要自己实现 P2P 网络?”

好处是显而易见的,如果出现问题,响应时间会极快,不会因为底层实现的复杂而打断项目进度,坏处也是显而易见的,我们是否有能力实现一个稳定高效的网络库,让它测试完善,并且功能与 rust-libp2p 的实现类似,易于扩展?同时如果真的实现出来了,时间成本、人工成本是否可控,如果实现了一年完成了第一个稳定版,那就没有什么意义了。终于在 11 月的时候,我们艰难地决定重新实现一个 P2P 网络库作为我们项目的网络依赖。平心而论,如果社区有稳定好用的库,我是不会再重复造轮子的,因为在正式环境使用久经考验的库毕竟会放心不少,哪怕底层的具体实现不了解。

开发过程

初期

一如既往,我选择 stable 版本作为稳定性的保证,然而这就意味着我拒绝了基于 nightly 的各种好用的新特性,比如心心念念的 async 语法糖等。好消息是 TheWarWar 同学已经将 yamux 协议开发得差不多了,虽然后期修了不少问题,但是在 11 月的时候,有个多路复用协议作为打底,心里着实踏实不少。当时,哪怕有 yamux 协议打底,我心里预计,第一个 demo 实现也需要一个月时间,因为菜所以怂。

先看过一遍 yamux 实现,提交几个 fix commit 之后,开始思索,一个能够挂载多种自定义协议 P2P 网络库,到底应该怎么去实现,从看过的 libp2p spec, 到看过的 rust-libp2p 实现代码,再到使用 tokio 的各种实战经历,再到 yamux 的实现、go-libp2p 的实现。思考了一周左右,单纯的思考,没有写一行代码,主要有几个问题:

  • 第一,如何组织代码,让事件流更加清晰;
  • 第二,如何让用户不用感知到底层的异步环境,能够以同步的方式去做事;
  • 第三,我觉得需要限制将复杂度暴露给用户,说的就是 rust-libp2p 的实现,对,它实现是足够灵活了,但是灵活的代价是复杂度极高的泛型签名,为了跑一个 demo 要花大量的时间去实现各种泛型方法(到目前为止,我依然抵制这种行为,但是我也理解它实现时为什么会使用如此多的泛型,最后堆砌出一个复杂度极高的 API);
  • 第四,其实对 futures、tokio 的套路不是特别熟悉,需要边研究边组织思路;

暂时想通了上面几个问题之后,花了一周写了第一版 demo 的实现(真正关于内部实现的问题,本次就不涉及了,后面的文章再详细说明)。比预计时间少了很多,非常惊喜,虽然在后面写测试的时候发现,确实存在不少 bug,边界条件没有考虑的问题,但是这些都不重要,重要的是,我发现,这东西比我想象中容易不少,突然就有信心继续完成这个库了。

现状

代码仓库:https://github.com/driftluo/p2p 或者 https://github.com/nervosnetwork/p2p

目前,各种功能测试加上了不少,加上协议本身的测试,以及我们内部也在尝试切换,也做了不少上层的测试,但是,我觉得还是不够。我现在比较担心的并不是功能上的 bug,我担心的是 poll 的调度问题,会不会存在调度不均匀的可能,虽然从目前的实现上来看,我对每次 poll 中 loop 的上限做了限制,哪怕其中一个协议的消息过于繁重,也不会导致陷入某个 loop 出不来,但是,这是在推导,并没有真正对多个任务繁重的协议进行真实的测试。这里实际上也是一个优化点,将 future 再次拆小,让小任务独立出去运行,让 tokio 的 work-steal 任务池去调度小任务。

特性来看,至少第一个小版本的特性算是差不多要冻结了,目前只支持 TCP 协议,后续应该会将其扩展到更多的底层协议,加密层也只支持一种加密算法。

我们期望当最简单的第一个版本稳定之后,再考虑扩展的问题,对于加密算法的扩展,本身就预留了可能性,而对于底层协议,目前我有一个模糊想法,大概需要增加两个 Trait 去实现,在我的想法里,这个扩展对当前 API 应该不存在破坏式的更改,这个留待以后实现。

大概很快,0.1 版本就会正式发布到 crate.io 上,如果有朋友对这个库有兴趣,欢迎 PR,欢迎提意见。

小结

不错,你们的感觉很到位,这篇没什么干货,纯属在漫谈,理一下开发过程中的小情绪,压力与成就感同在,真实的开发体验。后面的篇幅应该会重点在于内部各个小库的实现,这篇就当预告。