选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

本文整理自 CloudWeGo 开源一周年技能沙龙活动中字节跳动根底架构服务结构资深研制工程师吴迪的讲演分享,技能沙龙主题为《字节高功用开源微服务结构:CloudWeGo》。

本文将从以下三个方面介绍 CloudWeGo 开源的国内首个 Rust RPC 结构 Volo:

  1. CloudWeGo 挑选 Rust 言语进行探究的原因;

  2. 创立 RPC 结构 Volo 的原因;

  3. Rust 言语和 Go 言语怎么挑选。

  4. CloudWeGo 挑选 Rust 言语进行探究的原因

1. CloudWeGo 挑选 Rust 言语进行探究的原因

CloudWeGo 正式官宣新一代 Rust RPC 结构 Volo 开源!许多朋友会有疑问,CloudWeGo 为什么会挑选 Rust 这门言语进行探究呢?本文首要介绍一下其中的原因。

Volo 开源官宣:mp.weixin.qq.com/s/XcceLyKxW…

1.1 Go 的价值

  • 深度优化困难

Volo 前期的团队成员来自于 Kitex 项目(CloudWeGo 开源的 Golang 微服务 RPC 结构)。其时咱们投入了许多的时刻和精力优化 Kitex 以及其他相关根底库的功用,终究却发现完成 Go 的深度优化有些困难。咱们仅仅可以做一些算法层面和完成层面的优化,假如想往下持续做其他层面的优化,比方指令层面的优化,是很难以低本钱的方法完成的。而且在大多数状况下许多优化是要和 runtime 以及编译器作斗争的。

  • 东西链和包管理不行老练

例如,运用 Kitex 结构时需求先运用对应的 Kitex 东西生成代码,才干正常编译运用。尽管这种状况或许在 Frugal 东西老练之后有所改善,可是在 IDL 有更新的状况下,仍是需求运用 Kitex 从头生成对应的结构体。这个问题并不是 Kitex 的问题,而是 Go 言语本身的问题,Go 言语在编译时没有供给相似的才能。

  • 笼统才能较弱

Go 言语的笼统才能是比较弱的,而且 Go 言语里边的笼统并不是零本钱笼统,而是有价值的笼统。

那么运用 Go 言语需求支付的三个价值详细应该怎么了解呢?下面进行详细分析。

1.1.1 深度优化困难

如图所示,这是 Kitex 项目生成代码的简略示例。这两段代码的目的是在解析出错的时分,把一些信息返回给上层。在 Kitex 新版别代码揭露之后,事务团队同学反映他们线上序列化和反序列化这部分的功用相差了 20%,经排查之后,咱们发现了这个改动。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

Kitex 新版别的代码

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

Kitex 旧版别的代码

这个改动的原意是希望能给客户供给更多错误上下文的信息。可是它带来了什么问题呢?如下图,它把汇编代码直接一对一地生成到主流程之中,也便是说 Go 言语的编译器会逐行逐句地进行翻译,而且不会做重排。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

那么这会带来什么问题呢?由于咱们主流程中的代码与正常流程比较变多了,所以咱们要点重视一下L1-icache-load-misses这一行,新版别的代码比旧版别的代码在 L1 指令cache层面cache-misses高出 20%,这也便是咱们的代码功率下降 20% 的原因。那么咱们是怎么处理这个问题的呢?

咱们的处理方案如下图所示。在err != nil的状况下,直接手动加一条goto句子,把一切错误处理这部分的代码放到函数结尾,即return之后。这相当于在编译器没有完成指令重排的状况下,用人工方法做一次指令重排。最后优化的作用是十分显着的,可以看到cache-misses比之前的那一次还要下降 25%。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

上述比方仅仅运用 Go 言语时在做深度优化方面遇到的难题。在笼统才能方面,运用 Go 言语也会遇到一些困难。

1.1.2 零本钱笼统(Zero-Cost Abstraction)

什么是零本钱笼统呢?运用 C++ 和 Rust 的同学对这个概念或许有所了解。零本钱笼统是指咱们不需求对没有运用的功用支付编译和运行的开支,也便是用户不需求给没有运用的东西付费,对应地,假如用户关于现已运用的东西也没有再持续优化的空间,由于它现已默许供给了最佳实践。总结如下:

  • 不必的东西,不需求为之支付价值;
  • 用到的东西,你也不行能做得更好。

那么为什么说 Go 言语里边没有零本钱笼统呢?以 Thrift 编解码为例,咱们最开端运用的是 Apache Thrift,它为了支持多种不同 Protocol、Transport 组合,笼统出了 TProtocol Interface、TTransport Interface,但 Kitex 直接依靠详细的 BinaryProtocal 的完成(struct)。可以试想,Apache Thrift 这么做的价值是什么呢?这便是 Go 里边 Interface 带来的价值。

Go 里边 Interface 是动态分发的,也便是运行时经过类型元数据和指针去动态调用所需办法,它会在运行时多做一次内存寻址。但这并不是最要害的,最要害的是它会使得编译器没有办法 inline 以及没有办法做许多优化。一般比较注重功用的言语都会同时供给静态分发和动态分发两种方法的笼统才能,可是 Go 言语只供给了 Interface 动态分发才能,也就可以了解为在 Go 言语中笼统和功用是不行兼得的,这也便是 Go 言语笼统才能比较弱的原因。

1.2 Sonic

Sonic 是 CloudWeGo 开源的一个 JSON 库,这个库有许多 CloudWeGo 的用户都运用过。开端这个库组成部分如下图所示,有 2/3 的代码都是 Assembly 汇编。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

在 Sonic 库中仅有的 27% 的 Go 源代码如下图所示。尽管它被核算到了 Go 代码中,但实际上是汇编代码。所以咱们可以总结出,世界上最快的 Go 言语程序大概便是用汇编代码写就的。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

1.3 功用最好的 Go JSON 库

尽管 Sonic 里边采用了各种黑科技,甚至有 2/3 的代码都是经过人工精调的汇编代码,可是 Sonic 的归纳功用仍是不如 Rust 最通用的 Serde JSON 库。如图所示,绿色柱状图代表 Serde JSON 库,蓝色柱状图代表 Sonic 库。依据这个 Benchmark,即使是和 C、C++ 的库比较,用 Rust 言语编写的这个库在各方面归纳表现也是最佳的。

试想,又有多少 Go 组件可以得到如此许多的人力投入从而进行深度优化呢?这仅仅一个比方,其实咱们之前在 Kitex 中的许多优化也是要和编译器以及 runtime 作斗争的。因而咱们认识到在 Go 言语中想做深度优化是十分困难的。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

1.4 关于 Rust

咱们为什么要挑选 Rust 这门言语呢?在解答这个问题之前,要先了解这门言语。所以先介绍一下 Rust 言语的发展前史。

1.4.1 Rust 前史

Rust 言语由 Graydon Hoare 私人研制,他是 Mozilla 做编程言语的工程师,专门给言语开发编译器和东西集。其时 Mozilla 要开发 Servo 引擎,想要确保安全的同时又能拥有高功用,于是就挑选了 Rust 言语。2010 – 2015 年期间,Rust 是有 GC 的,后来社区一致表明支持 Rust 有必要要有高功用,所以 GC 被取缔。2015 年,Rust 发布 1.0 版别,这也表明正式官宣 Rust 的稳定性。

Rust 是以三年为单位进行社区规划和迭代的。2015 – 2018 年,Rust 达成了生产力的承诺,也便是它的东西文档还有编译器变得更加智能,也对开发者更加友好了。2018 – 2021 年,Rust 做了更多异步生态的完善。之前的 Rust 是没有异步生态的,可是自 2018 年开端,它正式引入了异步功用。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

1.4.2 Rust 2024

2021 – 2024 年,Rust 有一个 2024 规划,主题叫做 Scaling Enpowerment(扩展授权)。之所以取这个姓名,是由于 Rust 有一个方针——“empower everyone to build reliable and efficient software”。Rust 最重视也是咱们常常诟病的一点,便是 Rust 的整个学习曲线十分峻峭,所以在这个规划中写道 “Flatten the learning curve”。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

1.4.3 Rust 三大优势

在 2022 年,许多开源项目现已出现爆破式增长。咱们了解到 Rust 这门言语后,发现它有三大十分重要的优势:第一是高功用;第二是很强的安全性;第三是协作方便。因而咱们想尝试在服务端运用 Rust 言语开发微服务,以此处理咱们面临的一些功用上的问题。

  • 功用

许多用户都对功用有很高的要求,也想知道 Rust 的功用怎么。下图是各言语的 Benchmark 比照结果,可以看出 Rust 的功用是十分优异的,远超过 Go 言语,甚至比 C++ 的功用更好。

当然咱们要侧重说明,这个 Benchmark 要求一切言语有必要运用相同的算法,而且不得经过额定优化。究竟假如都用汇编代码写,其实各言语功用相差无几。可是在真实的开发进程中,又有多少代码可以经过那么许多的人工精细优化呢?别的,有人或许会对 Rust 的功用比 C 和 C++ 更优异发生质疑,其实这也是由于 Rust 关于程序员的输入要求得更加严厉,所以编译器可以做更进一步的优化。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

  • 安全性

由于在 Rust 言语的安全性方面可查阅到许多材料,因而不再过多赘述。只论述一个重要定论:Rust 1.0 之后,在非 Unsafe 代码中是不行能出现内存安全问题的。这个定论是经过数学证明过的,因而十分可靠。咱们应该怎么了解这个定论呢?可以从它的推论入手,即:一切内存 / 并发安全问题,都是 unsafe 代码导致的。也便是假如真的出现安全问题,咱们可以约束在一个十分小的范围内进行排查。由于究竟绝大多数的 Rust 言语代码都是 Safe Rust,而不是 Unsafe Rust。

  • 协作

Rust 是一门真实经过工程实践形成的言语,它有十分智能的编译器完善的文档集群的东西链老练的包管理,因而 Rust 十分适合协作。咱们在运用时可以专心于逻辑功用的完成,而不必忧虑内存安全和并发安全的问题等等。还有十分重要的一点便是可以约束他人的代码,由于假如他人的代码有内存安全问题或并发安全问题,将无法进行编译。所以在做 Code Review 时,咱们只需重视逻辑上的功用正确性就可以,由于只需可以经过编译提交上来的代码,安全性是不必忧虑的。这尽管是 Rust 言语的优点,但也给运用者带来一些不方便之处。咱们常传闻 Rust 开发者很难,也正是由于编译。

1.4.4 Rust 的影响力

如下图,Rust 现已接连七年位居 Stack Overflow 最受开发者喜爱的编程言语榜第一。此外,有一个十分重量级的项目叫做 “Rust for Linux”,除了 C 言语之外,Rust 是 Linux 内核迄今为止承受的仅有言语。这些成绩足以看出 Rust 在开源业界的重量级和影响力。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

2. 创立 RPC 结构 Volo 的原因

清晰了 CloudWeGo 挑选 Rust 言语的原因以及 Rust 的优势,我也论述一下发明 Volo 结构的原因以及 Volo 的特色。

2.1 生态现状

发明 Volo 结构与其时的生态状况是有关的。咱们其时调研过整个社区的生态,发现没有生产可用的 Async Thrift 完成。哪怕是社区中最老练的 Tonic 结构,它的服务管理功用也是比较弱的,而且易用性也不行强。更重要的是其时在 Rust 言语社区,还没有根据 Generic Associated Type(GAT,Rust 言语最新的⼀个重量级 Feature)和 Type Alias Impl Trait(TAIT,另⼀个重量级 Feature)的易用性强的笼统。

2.2 易用性

为什么独自说明 GAT 和 TAIT 这两个特性呢?按照 Rust 官方团队的说法,这是自 Rust 1.0 以来言语层面和 Type System 层面最大的变化。举例简略说明,下图是一个现有的社区方案,代码是没有运用 GAT 和 TAIT 的超时中间件的编写,咱们可以发现假如要确保功用不受损耗,需求编写许多代码。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

而在 Volo 结构中,由于采用了 GAT 和 TAIT 这两个特性,编写代码如下图所示。咱们可以显着比照出代码量和易用性方面的差距是十分显着的。Rust 以难学难用而闻名,咱们希望尽或许地下降用户运用 Volo 结构和 Rust 言语编写微服务的难度,供给给用户最契合人体工程学和直觉的编码体会,因而咱们把结构易用性作为重要方针之一。只需让咱们真实地运用 Volo,Volo 才干表现它的价值。所以 Volo 结构根据 GAT 和 TAIT 特性,大大提高了用户编写中间件的便利程度

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

除此之外,咱们供给了 Volo命令行东西生成默许 Layout,而且 Volo 的命令行东西供给IDL管理的才能,这在业界是首例。咱们还供给了进程宏等可以再度下降 Service 编写难度的功用。当然还有许多其他的精心设计,比方许多 API 都是尽量以最契合人体工程学的方法给出的,也可以避免误用。

2.3 扩展性

  • 根据 Service 的笼统

获益于 Rust 强大的表达和笼统才能,开发者可以根据十分灵活的 Service 笼统,用统一的形式对 RPC 的元信息请求和响应做一些处理,比方服务发现、负载均衡等服务管理功用都是直接完成 Service 即可。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

  • 根据RPC元信息的控制

别的,在咱们的结构设计中,一切结构行为都是受到 RPC 元信息控制的。因而咱们只需在 Service 中对 RPC 元信息进行修改,就能直接控制结构的行为,从而完成所需的功用。

下图是 Volo 自带的负载均衡中间件完成中最要害的一部分,即红色线框圈出的代码。只需把 Load Balance 选出来的地址放到 RPC 元信息中就可以,其他代码可以直接忽视掉。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

2.4 功用

假如过多谈论结构的功用比照,容易引战。可是根据 Rust 言语的功用优势以及 CloudWeGo 团队关于极致功用的追求,咱们可以预想到 Volo 的功用也是十分高的。

假如把 Volo 和 Kitex 进行跨言语的比照也是不太公正的,可是由于许多用户都重视功用数据,为了让运用者对 Volo 结构的功用有大致的了解,咱们只给出比较简略的功用数据。在与 Kitex 相同的测验条件(约束 4C)下,Volo 极限 QPS 为 35W。同时,咱们内部正在验证根据 Monoio(CloudWeGo 开源的 Rust Async Runtime)的版别,极限 QPS 可以达到 44W。

当然还有许多其他的功用目标,比方响应时刻也是十分影响用户体会的。所以除了 Benchmark,咱们选取了由 Go 迁移到 Volo 结构的两个事务,出现真实的事务落地收益。

事务 A(Proxy 类) 。A 事务的 IO 比较多,迁移到 Volo 结构后的各方面数据如下:

  • CPU Usage 630% -> 380%
  • MEM 9GB -> 2GB
  • P99 150-200ms -> 20-35ms
  • AVG 4-5ms -> 1.5ms

可以看出不论是 CPU、内存仍是延时的目标,都有十分显着的提高。下图中间红线代表 Volo 上线的时刻,也便是红线左侧这一部分是 Go 的目标,红线右侧是 Rust 的目标,左右比照可以更直观看出 Volo 结构给事务 A 带来的收益。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

事务 B(有许多事务逻辑) 。事务 B 是一个核算密集型的事务,运用 Volo 结构后 CPU 400% -> 130%。因而在核算密集型的事务中,CPU 的提高更加显着。

2.5 相关生态

跟着 Volo 结构开源,一起开源的一切生态如下:

  • Volo 是 RPC 结构的姓名,包含了 Volo-Thrift 和 Volo-gRPC 两部分。
  • Volo-rs 组织:Volo 的相关生态。
  • Pilota:Volo 运用的 Thrift 与 Protobuf 编译器及编解码的纯 Rust 完成(不依靠 protoc)。
  • Motore:Volo 参考 Tower 设计的,运用了 GAT 和 TAIT 的 middleware 笼统层。
  • Metainfo:Volo 用于进行元信息透传的组件,定义了一套元信息透传的标准。

全景图如下:

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

2.6 库房地址

以下是一切相关生态的库房地址。欢迎咱们来提 Issue 或 PR,一起共建 Volo!

  • Volo:github.com/cloudwego/v…
  • Volo-rs:github.com/volo-rs
  • Pilota:github.com/cloudwego/p…
  • Motore:github.com/cloudwego/m…
  • Metainfo:github.com/cloudwego/m…

3. Rust 言语和 Go 言语怎么挑选

了解 Volo 结构后,关于 Rust 言语和 Go 怎么挑选的问题,我有一些主观的建议和想法。

3.1 和 C++、Go 比照

假如 Go 的服务想用另一种言语重写,目前仍是 Rust 言语和 C++ 可选性高一些,因而我将这三种言语进行比照,以期为面临挑选编程言语的用户供给一些参考。

选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践

在学习难度方面,Rust 言语和 C++ 学习难度比较高,而 Go 言语的学习难度比较低。

在功用方面,Rust 言语和 C++ 的功用比较高。我给 Go 言语的功用评级为中等,究竟和 Python 这些服务比较,Go 言语仍是要强许多的。

在安全性方面,C++ 的安全性比较低,Go 言语安全性中等,Rust 言语安全性比较高。由于 Go 言语 尽管可以经过 GC 防住一些内存安全的问题,可是它没有办法防住相似 Data Race 这种并发安全的问题,而且大多数时分这类问题其实很难排查。Rust 可以做到可防可控,应防尽防,只需有内存安全问题或并发安全问题,都无法成功编译。

在协作方面,Rust 言语的协作才能比较高,Go 言语和 C++ 的协作等级是中等。首要,C++ 没有官方供给的包管理东西,它有必要借助第三方社区供给的包管理东西,可是不同的项目运用的包管理东西或许是不一样的,所以这是对用户来说十分不方便的;其次,在开发者可以确保自己的代码没有 Bug、契合最佳实践的状况下,仍是不行避免地会和一些第三方的库以及比较老旧社区一流的库发生交集,而且发生混用的景象;最后,假如涉及到大型项目,需求团队协作开发,咱们无法确保团队中其他人写出的代码也不存在内存安全问题。至于 Go 言语,它的编译时及东西链的才能相对来说比较弱,因而也定级为中等。

在特性和运用本钱方面,用户应该都有所了解,不再过多赘述。从运用本钱上来讲,我的评级为给 C++ 为高运用本钱,Go 言语和 Rust 言语的运用本钱是中等。C++ 的事务上线之后常常出状况,而且排查问题困难是很常见的状况。而运用 Go 言语做一些通用的编程是可以的,可是一旦涉及到定制化的需求在完成上就有必定的困难,比方需求依据不同的平台体系做体系级编程,运用 Go 言语做起来就十分麻烦。言语仅仅东西,咱们仍是要依据不同的场景选用更为适宜的言语。

那么 Go 言语和 Rust 言语的运用本钱为什么是中等呢?由于咱们不能只重视编写代码的功率,还要考虑运维和 Debug 的本钱。Go 言语或许也会发生 Panic,咱们内部也常常会有一些并发的问题,然后需求不断地排查。而 Rust 言语前置了这部分本钱,比较于其他言语结构在上线之后测验、确保稳定性,咱们把这部分的时刻精力用在了开发期间,这样也避免了线上事端带来的损失。因而我给 Go 言语和 Rust 言语鉴定的运用本钱是中等。

3.2 Rust & Go

假如将 Rust 言语和 Go 言语独自做比照,咱们应该怎么解读它们呢?这是一个十分经典的问题。可以尝试从以下四方面考虑:

  • 合作关系,取长补短

咱们团队认为其实二者并不是敌对关系,而是合作关系,它们是取长补短的。究竟言语仅仅东西,许多时分咱们仅仅需求一个更加得心应手的东西罢了。

  • (功用 >> 开发功率) || (安全性 >> 开发功率) -> Rust

关于需求极致功用,重核算的运用,以及需求稳定性并能承受必定开发速度损失的运用,引荐运用 Rust,Rust 在极致功用优化和安全性上的优势可以在这类运用中得以发挥。

  • 迭代速度要求高 -> Go

关于功用不敏感的运用、重 IO 的运用以及需求快速开发快速迭代胜过稳定性的运用,引荐运用 Go 言语,这种运用运用 Rust 并不会带来显着的收益。

  • 考虑团队技能储备和人才储备

当然,还有一个很重要的考虑因素,是团队现有的技能栈,即技能储备和人才储备。

4. 小结

希望以上内容能让咱们初步了解 Volo 以及相关的生态。目前 Volo 还处于前期发展阶段,欢迎各位感兴趣的同学参加咱们,共同建造 CloudWeGo 以及 Rust 开源社区。咱们诚意期待更多开发者参加,也期待 Volo 可以助力越来越多的企业快速构建云原生架构。