关于rust:选择-Go-还是-RustCloudWeGoVolo-基于-Rust-语言的探索实践

2次阅读

共计 8026 个字符,预计需要花费 21 分钟才能阅读完成。

本文整顿自 CloudWeGo 开源一周年技术沙龙流动中字节跳动基础架构服务框架资深研发工程师 吴迪 的演讲分享,技术沙龙主题为《字节高性能开源微服务框架:CloudWeGo》。

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

  1. CloudWeGo 抉择 Rust 语言进行摸索的起因;
  2. 创立 RPC 框架 Volo 的起因;
  3. Rust 语言和 Go 语言如何抉择。

CloudWeGo 抉择 Rust 语言进行摸索的起因

CloudWeGo 正式官宣新一代 Rust RPC 框架 Volo 开源!很多敌人会有疑难,CloudWeGo 为什么会抉择 Rust 这门语言进行摸索呢?本文首先介绍一下其中的起因。

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

Go 的代价

  • 深度优化艰难

Volo 晚期的团队成员来自于 Kitex 我的项目(CloudWeGo 开源的 Golang 微服务 RPC 框架)。过后咱们投入了大量的工夫和精力优化 Kitex 以及其余相干根底库的性能,最终却发现实现 Go 的深度优化有些艰难。咱们仅仅能够做一些算法层面和实现层面的优化,如果想往下持续做其余层面的优化,比方指令层面的优化,是很难以低成本的形式实现的。而且在大多数状况下很多优化是要和 runtime 以及编译器作奋斗的。

  • 工具链和包治理不够成熟

例如,应用 Kitex 框架时须要先应用对应的 Kitex 工具生成代码,能力失常编译应用。尽管这种状况可能在 Frugal 工具成熟之后有所改善,然而在 IDL 有更新的状况下,还是须要应用 Kitex 从新生成对应的构造体。这个问题并不是 Kitex 的问题,而是 Go 语言自身的问题,Go 语言在编译时没有提供相似的能力。、

  • 形象能力较弱

Go 语言的形象能力是比拟弱的,而且 Go 语言外面的形象并不是零老本形象,而是有代价的形象。

那么应用 Go 语言须要付出的三个代价具体应该如何了解呢?上面进行具体分析。

深度优化艰难

如图所示,这是 Kitex 我的项目生成代码的简略示例。这两段代码的目标是在解析出错的时候,把一些信息返回给下层。在 Kitex 新版本代码公开之后,业务团队同学反映他们线上序列化和反序列化这部分的性能相差了 20%,经排查之后,咱们发现了这个改变。

Kitex 新版本的代码:

Kitex 旧版本的代码:

这个改变的本意是心愿能给客户提供更多谬误上下文的信息。然而它带来了什么问题呢?如下图,它把汇编代码间接一对一地生成到主流程之中,也就是说 Go 语言的编译器会逐行逐句地进行翻译,并且不会做重排。

那么这会带来什么问题呢?因为咱们主流程中的代码与失常流程相比变多了,所以咱们重点关注一下 L1-icache-load-misses 这一行,新版本的代码比旧版本的代码在 L1 指令 cache 层面 cache-misses 高出 20%,这也就是咱们的代码效率升高 20% 的起因。那么咱们是如何解决这个问题的呢?

咱们的解决方案如下图所示。在 err != nil 的状况下,间接手动加一条 goto 语句,把所有错误处理这部分的代码放到函数开端,即 return 之后。这相当于在编译器没有实现指令重排的状况下,用人工形式做一次指令重排。最初优化的成果是非常明显的,能够看到 cache-misses 比之前的那一次还要升高 25%。

上述例子只是应用 Go 语言时在做深度优化方面遇到的难题。在形象能力方面,应用 Go 语言也会遇到一些艰难。

零老本形象(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 语言形象能力比拟弱的起因。

Sonic

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

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

性能最好的 Go JSON 库

只管 Sonic 外面采纳了各种黑科技,甚至有 2/3 的代码都是通过人工精调的汇编代码,然而 Sonic 的综合性能还是不如 Rust 最通用的 Serde JSON 库。如图所示,绿色柱状图代表 Serde JSON 库,蓝色柱状图代表 Sonic 库。依据这个 Benchmark,即便是和 C、C++ 的库相比,用 Rust 语言编写的这个库在各方面综合体现也是最佳的。

试想,又有多少 Go 组件可能失去如此大量的人力投入从而进行深度优化呢?这只是一个例子,其实咱们之前在 Kitex 中的很多优化也是要和编译器以及 runtime 作奋斗的。因而咱们意识到在 Go 语言中想做深度优化是十分艰难的。

对于 Rust

咱们为什么要抉择 Rust 这门语言呢?在解答这个问题之前,要先理解这门语言。所以先介绍一下 Rust 语言的倒退历史。

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 年开始,它正式引入了异步性能。

Rust 2024

2021 – 2024 年,Rust 有一个 2024 布局,主题叫做 Scaling Enpowerment(扩大受权)。之所以取这个名字,是因为 Rust 有一个指标——“empower everyone to build reliable and efficient software”。Rust 最关注也是大家常常诟病的一点,就是 Rust 的整个学习曲线十分平缓,所以在这个布局中写道“Flatten the learning curve”。

Rust 三大劣势

在 2022 年,很多开源我的项目曾经出现爆炸式增长。咱们理解到 Rust 这门语言后,发现它有三大十分重要的劣势:第一是高性能;第二是很强的安全性;第三是合作不便。因而咱们想尝试在服务端应用 Rust 语言开发微服务,以此解决咱们面临的一些性能上的问题。

  • 性能

很多用户都对性能有很高的要求,也想晓得 Rust 的性能如何。下图是各语言的 Benchmark 比照后果,能够看出 Rust 的性能是十分优良的,远超过 Go 语言,甚至比 C++ 的性能更好。

当然咱们要着重阐明,这个 Benchmark 要求所有语言必须应用雷同的算法,并且不得通过额定优化。毕竟如果都用汇编代码写,其实各语言性能相差无几。然而在真正的开发过程中,又有多少代码可能通过那么大量的人工精密优化呢?另外,有人可能会对 Rust 的性能比 C 和 C++ 更优良产生质疑,其实这也是因为 Rust 对于程序员的输出要求得更加严格,所以编译器能够做更进一步的优化。

  • 安全性

因为在 Rust 语言的安全性方面可查阅到大量材料,因而不再过多赘述。只论述一个重要论断:Rust 1.0 之后,在非 Unsafe 代码中是不可能呈现内存平安问题的。这个论断是通过数学证实过的,因而十分牢靠。咱们应该如何了解这个论断呢?能够从它的推论动手,即:所有内存 / 并发平安问题,都是 unsafe 代码导致的。也就是如果真的呈现平安问题,咱们能够限度在一个十分小的范畴内进行排查。因为毕竟绝大多数的 Rust 语言代码都是 Safe Rust,而不是 Unsafe Rust。

  • 合作

Rust 是一门真正通过工程实际造成的语言,它有十分 智能的编译器 欠缺的文档 集群的工具链 成熟的包治理,因而 Rust 非常适合合作。咱们在应用时能够专一于逻辑性能的实现,而不必放心内存平安和并发平安的问题等等。还有十分重要的一点就是能够限度他人的代码,因为如果他人的代码有内存平安问题或并发平安问题,将无奈进行编译。所以在做 Code Review 时,咱们只需关注逻辑上的性能正确性就能够,因为只有可能通过编译提交上来的代码,安全性是不用放心的。这尽管是 Rust 语言的长处,但也给使用者带来一些不便之处。咱们常据说 Rust 开发者很难,也正是因为编译。

Rust 的影响力

如下图,Rust 曾经间断七年位居 Stack Overflow 最受开发者青睐的编程语言榜榜首。此外,有一个十分重量级的我的项目叫做“Rust for Linux”,除了 C 语言之外,Rust 是 Linux 内核迄今为止承受的惟一语言。这些问题足以看出 Rust 在开源业界的重量级和影响力。

创立 RPC 框架 Volo 的起因

明确了 CloudWeGo 抉择 Rust 语言的起因以及 Rust 的劣势,我也论述一下发明 Volo 框架的起因以及 Volo 的特点。

生态现状

发明 Volo 框架与过后的生态状况是无关的。咱们过后调研过整个社区的生态,发现没有生产可用的 Async Thrift 实现。哪怕是社区中最成熟的 Tonic 框架,它的服务治理性能也是比拟弱的,而且易用性也不够强。更重要的是过后在 Rust 语言社区,还没有基于 Generic Associated Type(GAT,Rust 语言最新的⼀个重量级 Feature)和 Type Alias Impl Trait(TAIT,另⼀个重量级 Feature)的易用性强的形象。

易用性

为什么独自阐明 GAT 和 TAIT 这两个个性呢?依照 Rust 官网团队的说法,这是自 Rust 1.0 以来语言层面和 Type System 层面最大的变动。举例简略阐明,下图是一个现有的社区计划,代码是没有应用 GAT 和 TAIT 的超时中间件的编写,咱们能够发现如果要保障性能不受损耗,须要编写大量代码。

而在 Volo 框架中,因为采纳了 GAT 和 TAIT 这两个个性,编写代码如下图所示。咱们能够显著比照出代码量和易用性方面的差距是非常明显的。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。

当然还有很多其余的性能指标,比方响应工夫也是十分影响用户体验的。所以除了 Benchmark,咱们选取了由 Go 迁徙到 Volo 框架的两个业务,出现实在的业务落地收益。

1. 业务 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 带来的收益。

2. 业务 B(有大量业务逻辑)。业务 B 是一个计算密集型的业务,应用 Volo 框架后 CPU 400% -> 130%。因而在计算密集型的业务中,CPU 的晋升更加显著。

相干生态

随着 Volo 框架开源,一起开源的所有生态如下:

  • Volo 是 RPC 框架的名字,蕴含了 Volo-Thrift 和 Volo-gRPC 两局部。
  • Volo-rs 组织:Volo 的相干生态。
  • Pilota:Volo 应用的 Thrift 与 Protobuf 编译器及编解码的纯 Rust 实现(不依赖 protoc)。
  • Motore:Volo 参考 Tower 设计的,应用了 GAT 和 TAIT 的 middleware 形象层。
  • Metainfo:Volo 用于进行元信息透传的组件,定义了一套元信息透传的规范。

全景图如下:

仓库地址

以下是所有相干生态的仓库地址。欢送大家来提 Issue 或 PR,一起共建 Volo!

  • 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/…

Rust 语言和 Go 语言如何抉择

理解 Volo 框架后,对于 Rust 语言和 Go 如何抉择的问题,我有一些主观的倡议和想法。

和 C++、Go 比照

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

在学习难度方面,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 语言评定的应用老本是中等。

Rust & Go

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

  • 单干关系,舍短取长

咱们团队认为其实二者并不是对抗关系,而是单干关系,它们是舍短取长的。毕竟语言只是工具,很多时候咱们只是须要一个更加得心应手的工具而已。

  • (性能 >> 开发效率) || (安全性 >> 开发效率) -> Rust

对于须要极致性能,重计算的利用,以及须要稳定性并能承受肯定开发速度损失的利用,举荐应用 Rust,Rust 在极致性能优化和安全性上的劣势能够在这类利用中得以施展。

  • 迭代速度要求高 -> Go

对于性能不敏感的利用、重 IO 的利用以及须要疾速开发疾速迭代胜过稳定性的利用,举荐应用 Go 语言,这种利用应用 Rust 并不会带来显著的收益。

  • 思考团队技术储备和人才储备

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

小结

心愿以上内容能让大家初步理解 Volo 以及相干的生态。目前 Volo 还处于晚期倒退阶段,欢送各位感兴趣的同学退出咱们,独特建设 CloudWeGo 以及 Rust 开源社区。咱们诚心期待更多开发者退出,也期待 Volo 可能助力越来越多的企业疾速构建云原生架构。

我的项目地址

GitHub:https://github.com/cloudwego

官网:www.cloudwego.io

正文完
 0