我用 Rust 重写(并重新设计)了 frp,性能和资源占用有很大改善

2022-01-04 18:53:16 +08:00
 rapiz

rathole - 安全、稳定、高性能的内网穿透工具,用 Rust 语言编写。


先博一下眼球,让各位有兴趣继续看下去:

如果你对性能和内存的测试数据感兴趣,可以直接拉到帖子下面的图表感受一下

你可以先看看 README ,其中介绍了特性、配置和用法


下面写写我为什么要做这个工具,和我开发的时候在想什么。

frp 是优秀的基于内网穿透反向代理工具,方便易用,集成了很多功能。

但我对一个内网穿透工具的期望不太一样,我相信一个工具应该专注做一件事,通过简单优雅的设计接口来提供扩展能力。

我仔细思考了如何设计这样一个工具,它应该:

相比于 frp ,除了数据上的优势,rathole 还有更多的可能性:

未来

rathole 已经持续开发了一段时间,必要的特性已经趋于完善和稳定。我觉得是时候发布出来,让大家体验使用和反馈。

目前已经决定的开发计划有 HTTP API (或其他形式的 API )。API 实现后,能够支持 Dashboard 和复杂动态管理需求的开发。

最后,如果你还没有查看 项目 README ,这里是一个方便的链接

性能测试

测试的具体数字随机器变化,测试方法见此,主要用 iperf3 测试 TCP/UDP 吞吐量, vegeta + nginx 测试 HTTP 吞吐量,同时检测内存占用

23181 次点击
所在节点    分享创造
117 条回复
ragnaroks
2022-01-06 12:43:16 +08:00
release 中的 rathole-x86_64-pc-windows-msvc.zip ,里面除了可执行文件外还有个 deps 文件夹,这里面的可执行文件和根目录下的有什么区别?
ragnaroks
2022-01-06 12:51:13 +08:00
在 windows 10 1809 (server2019 / 1809ltsc) 下似乎不能正常运行,键入 rathole.exe --help 或者其它开关都没有任何输出
ragnaroks
2022-01-06 13:40:23 +08:00
看下了错误码是 -1073741515 ,应该是 nanoserver 缺少依赖的 dll 导致的,servercore 是可以跑的
rapiz
2022-01-06 16:16:20 +08:00
@ragnaroks 排查了一下,这来自使用的上游 Github Action 打包脚本的一个 Bug 。现在 release 已修复。 感谢反馈!
kknd22
2022-01-06 16:22:57 +08:00
frp 好像是中转吧,能先尽量尝试直连吗?
rapiz
2022-01-06 22:34:00 +08:00
HarveyLiu
2022-01-07 01:16:07 +08:00
@rapiz #86 点赞,效率,希望之后,维护,同步版本
zzl22100048
2022-01-07 13:25:57 +08:00
@xiaolanger meilisearch 不太适合大规模写入场景
xiaolanger
2022-01-07 14:02:24 +08:00
@zzl22100048 #88 写入不会太大,本来考虑的是 meilisearch 比 es 内存占用小,现在看来,好像不行?
rapiz
2022-01-07 19:57:57 +08:00
@HarveyLiu 已经是 CI 触发的了,自动推送更新
sgissb1
2022-01-10 23:09:50 +08:00
@nbndco 我不太认同你的观点。原因是逻辑和模块的设计实际上未必和性能挂钩。

真的能挂钩在一起的,我觉得更多是程序设计上的问题,我不认为有哪种语言可以做到绝对的灵活而且性能还能做到厉害到没什么语言可以打败的。

越简单就性能就是越好,越复杂就是性能很难做好。
首先 c 本来就不是面向对象,就没有太强的抽象能力,需要抽象能力的就要用 c 的近亲 c++(这里只谈论 c 和 c++)。那么很多时候都在说,c++的一些性能不如 c ,不是因为语言所致,而是模式所致( c++是部分面向对象,必然会有很多额外的开销,c 就不需要)。
go 接触过几天,rust 不了解,但据说语法各方面和 c++有一些相似。其实在我看来,你也可以去修改一个开源的 c++编译器,让编译出来的 release 代码做到最精简版本,你看性能好不好。另外 c++的开发维护难度高,还真不一定是模板问题,多数是 c++语言特性过于复杂,中二 c++程序员太多(过于自认为很了解 c++,搞出一堆问题来,不用模板,就一个继承和多态,在真实的项目中就能玩倒一片人)。

回到程序设计上来说,在程序设计的规模复杂度渐渐变大的时候,有时候会感到性能变差和维护变难。这个事情需要分两头看(排除写代码的人能力问题的话),一个是程序设计所致,另一个所谓的语言特性。

前者呢,可能是认为有一的变慢速度,比如一个事务,本身就要保证可靠性,那么一个巨大粒度的事务必然会比小粒度的慢。比如错误的使用多线程,导致竞争问题等等。
后者呢,比如一些代码需要编译成中间语言,运行的时候需要再做一次翻译,或者第一次运行需要翻译等等。

现在很多流行的语言高性能语言,说到底,就是把程序设计下沉到编译、解析层面。比如 go 的协程,就尽量的规避了中二程序员对线程在工程实践熟练度不高上引入的性能可能地下问题,然后由语言特性来包装起来,本质内部其实是固化了一定的程序设计模式在里面,简化和让程序员的程序设计模式更为确定和减少范围,以此来综合性的获得性能收益。所以 go 的早期版本里面为了解决调度、内存池、同步问题,自身的 runtime 里也有不少相关的 bug 在一点一点修改。

就像在 iot 或者低功耗设备里面,如果要用 python 、go 等等非 c/c++语言是完全可行的。但在一些特定功能或者模块上,用起来性能就有可能不如纯 c 的。为什么?一位内这些语言已经囊括了一定的程序设计模式,c 本身在这块会比他们更轻量级。

所以我还是觉得,本质还是程序设计的问题。过分强调某种语言如何如何我觉得没什么必要,关键在是好不好用,使用场景是否恰当,我记得这也是《设计模式》这本书里提到的一个重要观点。

我主要写 c++,但我也常常写 shell 和 python ,其实在很多地方,我就觉得 python 比 shell 、c++用起来更方便,shell 权限性大一些,c++太重。有些时候 python 性能不够的一些个人小服务,我会试着拿 go 来写,尽管 go 、的一些库的编译和依赖问题有时候让我比较抓狂,但奈何用起来方便,快捷。

真正商用场景,还是需要看哪一种语言用起来更合适,因为大家都有自己的特点。我以前也很喜欢推崇 c/c++,甚至觉得 vc 的方言版本就比 gnu 或者其他编译器的方言版本强的,但工程实践这么多年得到的经验,还是适用才最重要。
sgissb1
2022-01-10 23:11:45 +08:00
@sgissb1@nbndco 原因是逻辑和模块的设计实际上未必和性能挂钩。

这句话我更正一下,没写清楚。

我是想说,语言是语言,程序设计是程序设计。性能的好坏更多是程序设计所致,语言不是决定因素。
rapiz
2022-01-11 21:23:39 +08:00
@sgissb1 我觉得你可能需要学一下 Rust 再想想你这些观点。 @nbndco 关于零成本抽象的观点你好像理解的有点偏。
nbndco
2022-01-12 14:06:03 +08:00
@sgissb1

继承和多态都用不好的程序员完全不合格。我既然说了水平达到一定程度,这种入门的基础自然是已经完全理解并可以自然而然的应用的。我当然知道现在有些人自称程序员但是完全没入门,但是那个并不在考虑范围之内,rust 没有解决他们不会写正确的代码的问题,只是阻止了这些人写代码而已。

以下都假设是一个合格的程序员在写代码。

越简单就性能越好基本只对几百行的程序才可能为真(如果先不提 rust 的 zero cost abstraction 可能不会让代码变慢)。一旦代码复杂了之后,人脑能够理解并且不会发生错误的可能性太低了,必须要编译器来进行帮助。如果有些时候编译器帮助不了怎么办呢,只有两条路,要么就是写错误的代码,要么就是 over defensive (性能受损)。

我随便举两个例子,你既然对 C++比较熟悉,那么问:

1. 一个传入的指针,他的生命周期是什么?答案是,不知道,全靠文档和看源代码。

C++为了在语言层面上解决这个问题,只好加入 smart pointer ,但是并没有根本上解决这个问题,很多时候只是把所有的东西包在一个 rust 的 Arc<T>里了而已。

这带来两个问题:我们显然不能把一个传入的裸指针包进 shared_ptr 里,也就意味着所有的接口必须直接使用 shared_ptr ,即便某些情况下我们调用时事实上知道我们并不需要 shared_ptr ( enable_shared_from_this 我就不说了吧,基本给 shared_ptr 的正常使用判了死刑,说白了 smart pointer 除了 unique_ptr 以外其他的都完全没用);第二,如果接口没有用 shared_ptr 怎么办呢,所有权如此的不清晰使得我们只能把传入的数据拷贝一份,这个性能损耗就是非常惊人的了。

以上这些美好的设计还是建立在调用者和被调用者都正确地根据严谨清晰地文档实现了周期管理的前提下的,毕竟写错了也只有 runtime 的时候不时的报 segment fault 或者 leak 连错都没有。这点显然 Mozilla 的程序员水平完全不行,项目规模一大就不会写代码了,gecko 的内存泄漏问题一直都很严重,只好写个 rust 出来给自己擦屁股。

2. 一个类是否 thread-safe ?答案是,不知道,全靠文档和看源代码。

看文档,如果文档说了那就可以确定(吗?);如果文档没说,那么就只好去看源代码,如果没有源码,那就只能假设不 thread-safe 然后加锁了。为什么会这样呢?因为语言层面没有提供任何的机制来帮我们确定代码是否 thread-safe 。这个过程没必要的锁可能就不止一个了。没必要的锁可能发生在两层:1.类已经是 thread-safe 的了但我不知道; 2.虽然不是 thread-safe 但其实我们并没有任何 concurrent 的访问或者 mutable 的访问。

但是,老问题,以上这些美好的设计还是建立在调用者和被调用者都正确地根据严谨清晰地文档实现了 thread safety 的前提下。就算你今天是个优秀的程序员,通过仔细阅读代码确保了没有在一个 thread safe 的类访问再加一层锁,你有如何保证之后某个同事(甚至就是你自己)在修改这个类的时候把它改成不 thread-safe 的了呢?

我这几年写了一点 rust ,lifetime ,ownership 和 synchronization 基本都是我在 api 设计时候的本能反应了,依然还是会经常有各种编译器的错误,而且我看半天才能明白究竟哪里错了。

根本问题就出在,一旦代码复杂,人脑想要在 lifetime 和 synchronization 两个问题上想清楚不出错是不可能的,且不说你不可能通读所有代码并且不会想错任何东西,就说多人合作时大家各写各的改着改着你没动的代码就崩了。为了写出正确的代码,你只能 over defensive ,性能损耗随之而来。
nbndco
2022-01-12 14:10:20 +08:00
@sgissb1 以上所有的东西,我都没谈 Ergonomics 。

其实这个对性能影响也是根本性的。语言功能弱带来两个问题,一是代码可重用度低,从而优秀的算法不方便抽象成库广泛使用,二是正确的写法可能太麻烦,就懒得实现了。

但是理论上这不是性能损耗,因为你总是可以实现的么。只不过 rust 可能就是一两行,C 可能就要自己写几百行。
nbndco
2022-01-12 14:35:06 +08:00
@nbndco 忘了提抽象的问题了。

这些 over defensive 和 Ergonomics 本质上都是语言抽象能力弱,导致语义总是不清晰造成的。在 rust 里我可以写类似 `fn foo(arg: impl AsMut<dyn T + Send + Sync>)`来保证 thread safety 。这就使得我们可以在函数里避免所有无意义的锁(如果我们的类型不是 Send 和 Sync 的我们可以在调用的时候自己加上锁再传入就好了)。

在 C++里我就算放弃类型安全使用模版我都不知道该怎么实现这个函数。
nbndco
2022-01-12 14:42:32 +08:00
@nbndco 例子没仔细想,不一定 work ,可能要改成 `fn foo(arg: impl T + Send + Sync)` 更合理一点,但是意思是一样的。
sgissb1
2022-01-12 17:21:00 +08:00
@nbndco 我觉得讨论的有偏了,我们讨论的焦点在于两个:抽象成本、性能;会引出这个的原因,在于你提到语言特性会影响(决定?)这两个因素,我提出的是,我认为主要因素在于人,语言特性是次要的。

首先我还是不认为某个语言特性就决定了他的性能,人的因素比较大,结论的原因在于:
理论和工程实践很多时候就不在一个视角上,我并不了解你工程实践中大多数是什么情况,但是我的情况是,不管是 x86 汇编、c 、c++,横向对比(特定语言中),人的因素远远大于语言特征。

另外你说的 c++标准库的这些东西,我看上去感觉你是想阐述程序设计中的问题,比如你提到的抽象成本问题。这方面,我是认同的,但是问题来了,这又会导致什么性能问题呢?在 tr1 特性或者说 c++11 出来之前,类似智能指针、作用于锁、安全对象和非安全对象等等就已经有不少实践了。我能看到的是,其他语言在这些方面上提供很多丰富 runtime api 来完善这些,但不代表他们的实现就是最优的,或者说,不是第一个版本的实现在当时主流硬件+软件环境就是性能最优,也是需要多次迭代来实现。

实践这么久,写代码,就是谁做得多问题,是语言 runtime 一侧多做一些抽象和设计,还是使用者这一侧多做一点。
nbndco
2022-01-12 18:42:52 +08:00
@sgissb1 我赞成主要是看人,这个是没有任何疑问的,所以我的前提一定是合格的程序员。不过我理解你说的问题,你说的对,确实大多数情况下,性能问题是不会由切换语言而解决的,不存在因为我是 rust 写的所以它就更快更省内存。我自己在写 rust 的过程中也知道有些东西只是理论上会快一点,但是这些东西在 rust 里面都是没有任何心智成本的(不论是维护上还是实现上)。也确实有一些场景由于 rust 本身的很多特性使得我可以大胆地进行很多优化。
sgissb1
2022-01-13 10:56:51 +08:00
@nbndco 成功达成共识,不容易。感谢认同 ^_^

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/826182

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX