CloudWeGo Study Group 是由 CloudWeGo 社区发起的学习小组,开展以 30 天为一期的源码解读和学习活动,帮助新成员融入社区圈子,和社区 Committer 互动交流,并学习上手 CloudWeGo 几大框架项目。
CSG 活动第三期主要以学习 Rust 语言为背景,对 Volo 和 Monoio 源码进行解读。本期活动期间将会安排 4 期直播分享,围绕的主题分别为:
- Rust 语言的特性、CloudWeGo 选择 Rust 语言进行深入探索的原因以及 Volo 框架产生的初衷;
- Rustcc 聊天室:Volo 框架的设计原则、如何上手以及框架设计特点;
- Go 工程师如何尝试使用 Rust,如何从 Go 转型 Rust、调用 Rust 方法,Go 尝试和迁移使用 Volo 框架的使用成本和优点;
- 基于 io-uring 的高性能 Rust Runtime:Monoio 的技术特点及设计实现。
本文整理自 CSG 第三期第一场直播中字节跳动架构研发工程师刘捷分享内容。
01 嘉宾介绍
本次分享内容主要分为以下两个部分:
- CloudWeGo 选择 Rust 语言进行深入探索的原因;
- 创建 RPC 框架 Volo 的原因。
02 前言
大家可能或多或少都接触过 Rust 语言的相关信息。
前段时间,微软 Azure CTO 呼吁使用 Rust 代替 C/C++。Elon Mask 说自己是 Rust 的粉丝,但是却选择使用 C++。这种关于语言的口水战是长久以来都是不可避免的,大家理性看待就好。
在 Rust 语言领域,我们也可能经常会听到某个公司用 Rust 重写了某个项目,获得了哪些收益。毕竟在 Rust 语言里面有一句玩笑话,“一切能用 Rust 实现的,终将会用 Rust 重写”。
我们也经常会听到关于 Rust 语言的初创公司获得融资的消息。
除了这些在 Rust 语言编写之外的信息,我们也会接触到编写 Rust 的相关信息,比如与编译器斗智斗勇、泛型约束过长以及强大的宏编程。
03 深入探索 Rust 的原因
通过了解以上信息,大家已经有了对于 Rust 语言的初印象。那么 CloudWeGo 为什么要选择 Rust 这门语言进行深入探索呢?这不得不提到 Rust 的发展历程。
Rust 发展历程
Rust 语言由 Graydon Hoare 私人研发,他是专门给语言开发编译器和工具集的工程师,他希望开发一种保证安全的同时又能拥有高性能的语言。2006 年,他开始进行研发。2008 年,他被 Mozilla 聘用,当时 Mozilla 要开发 Servo 引擎,想要保证安全的同时又能拥有高性能,于是他们一拍即合,Graydon Hoare 得到了 Mozilla 的赞助。2010 年,Rust 语言首次发布 0.1.0 版本,用于 Servo 引擎的研发。2010 - 2015 年期间,Rust 经历了 GC 移除等一系列演进,以保证高性能。2015 年,Rust 发布 1.0 版本,这也表示正式官宣 Rust 趋于稳定。2015 - 2018 年,Rust 达成了生产力的承诺,也就是它的工具文档还有编译器变得更加智能,也对开发者更加友好了。2018 - 2021 年,Rust 做了更多异步生态的完善,这也使得这门语言渐渐走向成熟。
Rust in 2022
2022 年已经是 Rust 连续第七年位居 Stack Overflow 最受开发者喜爱的编程语言榜榜首。
为了给管理和开发 Rust 项目的维护者提供一些支持,Rust 成立了基金会。有了 AWS、谷歌这些大公司的支持,Rust 的发展也有了一定的保障。同时,Rust 即将融入到 Linux 6.1 版本中,除了 C 语言之外,Rust 是 Linux 内核迄今为止接受的唯一语言,这足以看出 Rust 在业界的重量级和影响力。
此外,作为 WebAssembly 领域的首选语言,由 Rust 编写的 WebAssembly 运行时 Wasmtime 发布了 1.0 版本,这也意味着它达到了稳定状态和生产可用性。
最近 Reddit 的论坛上 Rust 的订阅人数突破了 200K,首次超越了 Go 语言的订阅者人数。
Rust 2024
Rust 的未来发展会是什么样子的呢?借助 Rust 官方团队成员的一句话来说明,“the year of everywhere”,也就是说使用 Rust 语言编写的开源项目会有爆发性的增长。那么为什么可以预测它会被大范围使用呢?这完全得益于 Rust 对于易用性的建设规划。2021 - 2024 年,Rust 有一个 2024 规划,主题叫做 Scaling Enpowerment(扩展授权)。之所以取这个名字,是因为 Rust 有一个目标——“empower everyone to build reliable and efficient software”。Rust 最关注也是大家经常诟病的一点,就是 Rust 的整个学习曲线非常陡峭,所以在这个规划中写道 “Flatten the learning curve”。
Rust 优势
CloudWeGo 选择 Rust 语言进行探索,是因为 Rust 语言具有以下三大特性。
高性能
- 基于 RAII 的内存管理
RAII 是将内存资源和对象绑定在一起。在创建对象时将资源分配出去,在销毁对象时把内存资源直接释放掉。相比于 JAVA / Go 等带有 GC 的语言,在 Rust 语言中,失效对象相对应的内存资源可以得到立马释放,不需要等待某一个时间点由 GC 进行统一的内存回收,避免了性能损失。
- 严格的约束
Rust 语言的译器对程序员的代码要求会更严格,除了实现正常功能之外,它还能获取到一些额外的编译信息,以辅助编译器做极致的优化。
- 零成本抽象
零成本抽象可以理解为你用不到功能,就不需要为之付出代价。例如,Rust 这门语言是几乎没有运行时的,也就是说你在实现一个纯同步代码的功能时,产物里面不会包含异步运行时的开销。
下图是从各语言 Benchmark 里面截取的纯计算 case。可以看出 Rust 的性能是非常优秀的,可以与 Go 语言、C++ 等相媲美。
可靠性
内存安全
内存安全是由两方面保障的,分别是所有权和生命周期。
- 所有权
所有权指的就是任何资源有且只能有一个绑定对象。如下图所示,这相当于栈上有一个 S1 指针,指向了堆上面的一块内存资源。之后把 S1 复制给 S2,这个行为在不同语言中有不同的处理方式。在其他语言中,这一次的复制行为就会默认是一次拷贝,此时 S1 和 S2 都可以访问那一块堆上的内存资源。但是在 Rust 语言中,这是一次移动语义,也就是说这次复制把这一块内存资源的所有权从 S1 转移到了 S2,S1 也会因此失效而无法使用。
所以把它打印出来使用的时候,编译器就会报错,这个值已经被移动了。这解决了内存安全中的第一个痛点,即对于同一块内存资源的二次释放问题。
- 生命周期
生命周期是对于引用的限制,它保证的是被引用的对象存活时间一定长于引用者。这就意味着你引用的对象一定是有效的。如下图所示,r 引用了 x,但是在第六行对 r 进行操作的时候,x 已经在大括号的作用域里面消亡了。
此时编译器也会报错。这就解决了内存安全中的第二个痛点———悬垂指针的问题。
并发安全
Rust 语言中是通过 Send 和 Sync 这两个标记的 Trait 实现的,Trait 可以理解为编程语言里面的一个接口。Send 和 Sync 里面都是没有任何方法声明以及方法体的,所以二者仅仅是作为标记信息提供给编译器,辅助编译器拒绝一些线程不安全的代码。
- Send: 实现 Send 的类型可以在多线程间转移所有权。
这个拒绝的过程是怎样的呢?以在并发安全里最常见的 Data Race 为例,在不同线程间对同一个变量进行读写的数据争用,下图中 spawn 方法代表的含义是在 Rust 里面开始一个线程。
我们可以看到它对于函数 F、类型 T 都有 Send 的约束。这个 Send 的意义是实现 Send 类型,它可以在多线程间转移所有权,也就是说现在有线程 A 和线程 B,如果 T 的所有权从线程 A 转移到了线程 B,那么可以避免线程 A 和线程 B 同时对 T 进行读写操作,以免发生数据争用。
- Sync: 实现 Sync 的类型可以在多线程间共享引用。
类型 T 所映射的是引用,这就是 Sync 这个标记可以发挥的作用。如果是一个引用的类型需要实现 Send,那么就必须保证这个类型实现了 Sync 标记,以此避免数据竞争。
想了解更多关于 Send 和 Sync,可以查看下方链接。
The Rustonomicon: https://doc.rust-lang.org/nom...
生产力
Rust 语言是一门工程实践语言。它不需要遵循某一个特定的编程范式,开发这门语言的编程人员也同时在用这门语言完成一些生产工作。因此 Rust 语言有以下四个特点:
- 智能的编译器
调查研究表明,由于 Rust 语言的编译器报错信息更加详尽智能,使用 Rust 语言的开发者在开发过程中 Stack Overflow 使用更少一些。
- 完善的文档
Rust 秉持“注释即文档”的理念。如果你想用某个库实现一定的功能,通常按照它的示例说明就可以快速上手。
- 齐全的工具链
Rust 是一个多平台支持的语言。
- 成熟的包管理
它可以帮助你更好地处理一些依赖。而且每当遇到一个新项目,你能够快速找到一个入口去上手。
最后,最重要的一点是,如果使用 Rust 这门语言,你完全可以信任别人的代码。因为如果别人的代码里有内存安全或者并发安全问题,基本都是编译不过的。
Benefits
了解 Rust 这门语言的三大优势后,我从个人的角度出发,分享一些 Rust 语言值得被选择的理由。
第一,Rust 帮助你成为更好的程序员。Rust 这门语言足够底层,毕竟它可以融入到 Linux 内核中。在编写 Rust 的过程中,我们不可避免地会接触到一些底层相关的知识。此外,Rust 的编译器非严格。在写代码的过程中,它会强制你去思考,从你所写的代码一直思考到内存堆栈引用、变量作用域等一系列内容。
第二,互联网的野蛮生长迟早会到尽头,硬件的摩尔定律也在失效,后续可能就是存量优化的阶段,这个时候 Rust 这门语言就非常合时宜了。
第三,Rust 语言开发的项目会更稳定,也更好维护。因为它把运行时可能出现的一些问题提前到编译时期进行处理。
Go or Rust
如果把 Go 语言和 Rust 语言进对比,结果会是怎样的呢?
如下图所示,无论是出现时间,发展历程还是适用领域,二者都相差不大,但二者并不是对立关系,而是合作关系,它们是取长补短的。毕竟语言只是工具,很多时候这个工具只要使用起来得心应手就可以。
2021 年 Go 开发者调研的报告显示,如果在新项目中选择代替 Go 语言的另一种语言,选择 Rust 语言的占比是最高的。
那么在使用这两种语言时应该如何选择呢?
一般对于性能不敏感的应用、重 IO 的应用以及需要快速开发快速迭代胜过稳定性的应用,推荐使用 Go 语言,这种应用使用 Rust 并不会带来明显的收益。
而对于需要极致性能、重计算的应用,以及需要稳定性并能接受一定开发速度损失的应用,推荐使用 Rust,Rust 在极致性能优化和安全性上的优势可以在这类应用中得以发挥。
在初步了解 Rust 语言后,对 Rust 语言感兴趣的同学可能希望能够动手实际操作一下,所以接下来介绍一下 Rust 的安装和使用。
Rust 安装
- 环境变量
我们一般使用 RUSTUP 进行 Rust 工具链的安装和管理。一般可以先在你的系统上配置这两个环境变量,下面是我们团队维护的 RUSTUP 镜像,它可以加速在国内的下载。
RUSTUP_DIST_SERVER="https://rsproxy.cn"
RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
- 安装
安装分为以下两种情况:
第一,如果是 Linux 以及 MacOS 系统,在终端执行这条命令即可。
Linux/MacOS:curl --proto '=https' --tlsv1.2 -sSf https://rsproxycn/rustup-init.sh | sh
第二,如果是 Windows 系统,下载下方链接中的 exe 可执行文件,后续按照默认的安装配置操作即可。
Windows:https://static.rust-lang.org/...
- 验证
安装成功的验证是在终端以及命令行里面输入 rustc -version,可以看到按照以下格式显示的最新稳定版本的版本号、对应的 Commit Hash 和 Commit 日期:
rustc x.y.z(abcabcabc yyyy-mm-dd)
这就代表已经安装成功。
环境配置
安装成功后要进行环境配置,配置文件:
IDE
Rust 有 VS Code 和 Clion 两种编辑器可供选择。
VS Code
如下图所示,VS Code 只需安装 Rust Extension Pack 扩展包即可,其中最重要的是 rust-analyzer,它作为一个语言的服务器后端,能够帮助你进行代码诊断。下图左侧的其他部分是比较推荐的 Extension,可以用来辅助开发,其中 CodeLLDB 是 Rust 用来 Debug 的工具。
图中右侧是比较推荐的 VS Code 设置。其中第三个设置在保存代码文件时,可以改成 clippy,也就是检查工具,它可以帮助改善你的代码。将第四个设置中 build.rs 打开,可以帮助你在执行项目代码之前编译执行 build.rs 的脚本。最后一个设置是关于 Rust 过程宏,能够帮助你能够更好地写宏以及看别人写的宏。
Clion
Clion 需要安装 Rust 插件,安装之后打开图中所示 build.scripts 脚本选项即可。
Hello World
最后就可以正式进行 Hello World Demo 的使用了,可以按照以下步骤进行:
- 创建新项目: cargo new he ello_world
- 使用 VS Code 打开: codel hello_world
- 跑起来: cargo run
我们可以看到下图中 Hello World 的项目结构。src 是源码目录,main.rs 里面有 main 函数。
Cargo.toml 可以帮助你管理包的信息以及依赖的引入。Cargo.lock 基本无需改动,它是 Cargo 自动生成的,可以帮助你管理。target 里面是一些编译产物。
学习资源
- The Book: https://doc.rust-lang.org/book/
- Rust By Example: https://doc.rust-lang.org/rus...
- Rustlings: https://github.com/rust-lang/...
- Async Rust: https://rust-lang.github.io/a...
- 汇总: https://github.com/ctjhoa/rus...
04 创建 Volo 的原因
Volo 是字节跳动服务框架团队研发的轻量级、高性能、可扩展性强、易用性好的 Rust RPC 框架,使用了 Rust 最新的 GAT 和 TAIT 特性。在字节内部,Volo 已经落地多个业务和基础组件,并且取得了超预期的性能收益。
明确了 CloudWeGo 选择 Rust 语言的原因以及 Rust 的优势,我也详细阐述一下创造 Volo 框架的原因。
生态现状
创造 Volo 框架与当时的生态情况是有关的。我们当时调研过整个社区的生态,发现没有生产可用的 Async Thrift 实现。哪怕是社区中最成熟的 Tonic 框架,它的服务治理功能也是比较弱的,而且易用性也不够强。更重要的是当时在 Rust 语言社区,还没有基于 Generic Associated Type(GAT,Rust 语言最新的⼀个重量级 Feature)和 Type Alias Impl Trait(TAIT,另⼀个重量级 Feature)的设计。
GAT & TAIT
GAT 和 TAIT 是目前只能在 Nightly Rust 上使用的特性,这两个特性也是 Rust 官方团队“2021 - 2024 三年规划”中三项重点工作其中两项。感兴趣的同学可自行进一步了解。
一个 Timeout 中间件
从下图我们可以直观地看到,如果采用之前社区的方式来编写一个最简单的 Timeout 中间件,在性能不损耗的情况下,代码量也是非常大的。在使用 GAT 和 TAIT 特性后,不仅代码量减少一倍,而且逻辑也更加简单清晰了。
Volo 优势
易用性
Rust 以难学难用而闻名,我们希望尽可能地降低用户使用 Volo 框架和 Rust 语言编写微服务的难度,提供给用户最符合人体工程学和直觉的编码体验,因此我们把框架易用性作为重要目标之一。只有让大家真正地使用 Volo,Volo 才能体现它的价值。所以 Volo 框架基于 GAT 和 TAIT 特性,大大提升了用户编写中间件的便利程度。除此之外,我们提供了 Volo 命令行工具生成默认 Layout,并且 Volo 的命令行工具提供 IDL 管理的能力,这在业界是首例。我们还提供了过程宏等能够再度降低 Service 编写难度的功能。当然还有很多其他的精心设计,比如很多 API 都是尽量以最符合人体工程学的方式给出的,也可以避免误用。
扩展性
- 基于 Service 的抽象
受益于 Rust 强大的表达和抽象能力,开发者可以基于非常灵活的 Service 抽象,用统一的形式对 RPC 的元信息请求和响应做一些处理,比如服务发现、负载均衡等服务治理功能都是直接实现 Service 即可。
- 基于 RPC 元信息的控制
另外,在我们的框架设计中,所有框架行为都是受到 RPC 元信息控制的。因此我们只要在 Service 中对 RPC 元信息进行修改,就能直接控制框架的行为,从而实现所需的功能。
下图是 Volo 自带的负载均衡中间件实现中最关键的一部分,即红色线框圈出的代码。只要把 Load Balance 选出来的地址放到 RPC 元信息中,就可以控制框架对这个对端地址发起请求。
高性能
如果过多谈论框架的性能对比,容易引战。但是基于 Rust 语言的性能优势以及 CloudWeGo 团队对于极致性能的追求,我们可以预想到 Volo 的性能也是非常高的。
如果把 Volo 和 Kitex 进行跨语言的对比也是不太公平的,但是因为很多用户都关注性能数据,为了让使用者对 Volo 框架的性能有大致的了解,我们只给出比较简单的性能数据。
- 在与 Kitex 相同的测试条件(限制 4C)下,Volo 极限 QPS 为 35W。
- 我们内部正在验证基于 Monoio(CloudWeGo 开源的 Rust Async Runtime)的版本,极限 QPS 可以达到 44W。
实际业务收益
在框架的实践过程中,我们尽可能在编译期就确定类型大小,避免在堆上的内存动态分配。同时在数据传输以及转换的过程中,我们会尽量减少内存拷贝。我选取了由 Go 迁移到 Volo 框架的两个业务,呈现真实的业务落地收益。
1. 业务 A(Proxy 类)。A 业务的 IO 比较多,是 IO bound 类型的业务,迁移到 Volo 框架后的各方面数据如下:
- CPU: -39.68%
- MEM: -77.78%
- P99: -76.67%
- AVG: -70%
2. 业务 B(有大量业务逻辑)。业务 B 是一个计算密集型的业务,是 CPU bound 类型的业务,使用 Volo 框架后数据如下:
- CPU: -43.75%
- MEM: -69%
- P99: -43.22%
- AVG: -47.81%
因此我们可以总结,不论是 IO bound 还是 CPO bound 类型的业务,使用 Volo 框架后 CPU 、内存和延时指标都有明显的优化。
相关生态
随着 Volo 框架开源,一起开源的所有生态如下:
- Volo 是 RPC 框架的名字,包含了 Volo-Thrift 和 Volo-gRPC 两部分。
- Volo-rs 组织:Volo 的相关生态。
- Pilota:Volo 使用的 Thrift 与 Protobuf 编译器及编解码的纯 Rust 实现。
- Motore:Volo 参考 Tower 设计的,使用了 GAT 和 TAIT 的 middleware 抽象层。
- Metainfo:Volo 用于进行元信息透传的组件,定义了一套元信息透传的标准。
Volo 相关地址
Volo 的项目结构如下图红线框圈出部分所示。如果想对 Volo 做贡献,可以多多关注 volo-thrift/volo-grpc/volo。同时我们也提供了大量的 Good First Issue,大家可以以练代学,参与到 Volo 的建设中来,我们也会给大家提供一些详尽的帮助。以下是所有相关生态的仓库地址。
欢迎大家来提 Issue 或 PR,一起共建 Volo!
- 官网: https://www.cloudwego.io/zh/d...
- Good First Issue: https://github.com/cloudwego/...
- Volo: https://github.com/cloudwego/...
- Volo-rs: https://github.com/volo-rs
- Pilota: https://github.com/cloudwego/...
- Motore: https://github.com/cloudwego/...
- Metainfo: https://github.com/cloudwego/...
Demo
Rust RPC 框架的调用链如下图所示:
具体调用 Demo 演示过程请参考直播视频回放链接:(从 41:18 处开始)
https://meetings.feishu.cn/s/...
Demo 相关官网文档:
https://www.cloudwego.io/zh/d...
以上内容整理自 CloudWeGo 源码解读活动第三期第一场直播分享,获取讲师 PPT 和回放视频,请关注 CloudWeGo 公众号,并在后台回复关键词 “Volo”。
项目地址
GitHub:https://github.com/cloudwego
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。