Rust 从入门到放弃,再入门到贡献 nacos-sdk-rust
Rust 上手难度大?我想是的。从文章标题便可知一二,小编水平有限经历了多次入门,得来的经验之谈。本文不涉及详细的技术剖析,仅表达入门的心路历程,供客官参考。
1、初次入门
初听 Rust 性能彪悍,内存安全,对比 C 来的保证不会有内存泄漏;也就是说 Rust 有 C 同等的性能而又保证了内存安全。牛!!!故而尝试开启了初次入门之旅。
当时从多方搜索引擎中涉猎 Rust 相关话题和知识,ATA、知乎、Bilibili、科学上网等。
也不记得是在哪个渠道,看到说 Rust + WebAssembly 是下一代云计算的未来。
于 2022-01-17 写了如下的第一行 Rust 代码
涉猎的资料五花八门,断断续续零零碎碎,也没有持续地编码参与项目开发,对所有权、生命周期这些核心概念没有领悟,宣告了初次入门失败
2、再次入门
历经了很长一段在放弃与相关话题的持续关注之间,还是好奇心作怪,发现最佳入门辅助手册 《Rust语言圣经(Rust Course)》,仅代表小编自我观点(英文水平不足等等)。
多次翻阅『圣经』,Rust 基础入门、所有权/借用、特征对象、生命周期、异步编程、Cargo 包管理等。搭配看一些开源社区的项目:sentinel-rust
/skywalking-rust
/opentelemetry-rust
/dubbo-rust
/… 于实际项目中的运用。
在 skywalking-rust
知道 tonic 的使用及查看其构建 api,发觉作为 grpc 客户端并不需要 build_server 只留 build_client,故而笔者提交了第一行 Rust Chore(tonic-build): set tonic-build.build_server(false), do not build Server code.
『圣经』推荐了好几个不错的组件:
serde-rs/json
超高性能的通用序列化/反序列化框架,快到上天的 JSON 库,Rust 序列化 json 的事实标准。tonic
完全 Rust 实现的 gRPC 客户端和服务器端。tokio-rs/tokio
最火的异步网络库,除了复杂上手难度高一些外,没有其它大的问题。tokio 团队提供了多个非常优秀的 Rust 库,用户认可度很高。tokio-rs/prost
tokio 出品的 Protocol Buffers 工具,简单易用,文档详细。tokio-rs/tracing
强大的日志框架,同时支持 OpenTelemetry 格式,无缝打通未来的监控。
感觉时机半熟,由此决定为 Nacos 开发 Rust 版的客户端,亦作为自己上手的目标去牵引持续下去。
3、nacos-sdk-rust
始于 2022-07-06,提交 README.md 说明 Nacos 客户端与服务端的交互逻辑,客户端需要实现的能力。
简要描述 client & server 的交互
请关注 proto/nacos_grpc_service.proto 并知晓构建出客户端侧的 stub,实现同步调用 service Request.request(),流式交互 service BiRequestStream.requestBiStream()。
tonic 创建与 Nacos-server 的 gRPC 双工长链接,serde/json 适配与 server 的交互序列化;
gRPC 交互的 Payload 和 Metadata 由 Protocol Buffers 序列化,具体的 Request/Response 实体 json 格式二进制数据维护于 Payload.body,类型名字符串维护于 Metadata.type 。
有了 gRPC 双工长链接,也有了数据序列化方式,那么就是对 Request/Response 的处理逻辑啦; 而 client 会接受 server 的主动调用,故可以实现一个通用的 RequestHandler 接受 server 的请求,根据 Request 类型分发到具体的处理实现并返回对应的 Response。
而 client 请求 server 的部分,则 do it …
以上交互务必参考 java nacos-client 和 nacos-server 的实现。
3.1、异步/跨线程是极大门槛
如上的简要描述中,模型定义及序列化,朴素的编码都能写出来,而难点在于 gRPC 的交互,客户端构建后如何跨线程被使用?既能在需要的时候拿到它发起调用,又能实时捕获到服务端返回的数据然后作出处理并回应?在所有权/生命周期等约束下,异步/跨线程等应该是个极大的门槛!
遇到了难题焦头烂额,期间中断了提交,有些消沉而又继续抱着『圣经』看看并在 tracing 订阅了解中看到 tokio-rust/console
,好奇它是怎么和应用交互并能实时地返回信息显示在界面上?
阅读它的源码找准了关键点,tonic 发起于应用的交互然后并发线程 loop 取得 gRPC 持续地等待服务端推送数据,然后执行逻辑。搭配看起来就很牛,谁先返回则执行其分支的 tokio::select! { xx=>, yy=>, zz=> }
- 分支1:等候 gRPC 服务端来的数据,使用
tx: Sender
将其发往mpsc::channel
; - 分支2:等候
rx: Receiver
接收分支1中的数据并处理; - 分支3:以上两个分支均不执行,则处理一些日常逻辑。
俗话说参考借鉴(抄袭)tokio-rust/console
中这部分的逻辑(拷贝过来改改)能用!数据交互不堵塞,还会断线自动重连。
3.2、gRPC 交互改用
逻辑是写好了,发觉运行不起来!总是卡在了什么地方,研究了一段时间,看 tonic
的 api 粗浅理解每次发起请求只能丢进去一个有界的 stream 流,这点在 Nacos 的交互模式里会非常难(客户端发起 stream 请求后,客户端/服务端都不会 close 结束它,而是一直持有它用作后续服务端主动推通知)决定了当前不合适。挣扎了蛮久无界流则没能发出请求,有界流难道客户端要无限重发请求?
再从『圣经』知道 tikv/grpc-rs
也是优选的 gRPC 客户端,它 wrap 了 gRPC 官方 C++ 库,看看 api 感觉可以,便尝试了确实好用。所以替换 tonic
改用 tikv/grpc-rs
。
3.3、加入 nacos-group
从第一行 nacos-sdk-rust 开始,两个月处于学习中的断断续续提交,然后提案由 Nacos PMC 认可故而迁入 nacos-group;随即发布了第一版 nacos-sdk=0.1.1,支持配置获取与订阅、客户端自动重连。
后来有 @onewe 同学的加入负责服务注册模块功能的开发,和我“先能用”的想法不同,他要求高,直接就从宏入手,交互模型的通用部分由宏(nacos-macro)统一生成,模型和 grpc 交互都重新定义了一番。
在框架内自定义 Event 事件抽象中 @onewe 使用了 TypeId
+ Any
,居然有反射的韵味!大哥我大受震惊,可真是个小宝贝。
小编思维局限于 loop tokio::select!{}
一直没能优化 gRPC 交互逻辑,后来也是由 @onewe 新构建了这部分,并能自动重连、解决同一地址 gRPC 默认复用同一链接的问题。
最后历经近百次提交,完善并发布了 nacos-sdk=0.2.x 配置管理/服务注册功能均可用,包括登陆鉴权、配置解密插件。
4、总结
推荐入门辅助手册 《Rust语言圣经(Rust Course)》;
总的来说 Rust 入门需要花费一些时间,最好有持续的目标去牵引。参与开源社区 Rust 项目是个相对较好的方式,会有小伙伴与你一道前行。或许你还能收获一份友谊,虽友人素未谋面。
入门后方能发觉现代不少数据库都使用 Rust 语言开发,不限于分布式存算分离 HTAP 数据库 TiDB 之下 TiKV,云原生数仓 Databend,蚂蚁开源的 CeresDB,创业中的时序数据库 GreptimeDB;亦有很多其他基础设施,微内核虚拟化安全容器 Kata-Containers,隐私计算 Teaclave (Apache incubating) and so on…
大名鼎鼎的 Linus(竖中指.jpg)都已接纳 Rust on Linux,你不来试试嘛?