近日, Qcon 全球软件开发大会 2022(上海站)圆满落幕,大会是由 InfoQ 中国主办的综合性技术盛会,近百位国内外技术大咖现场分享前沿技术案例与创新实践。本文整理自字节跳动火山引擎基础架构服务框架工程师吴迪于大会上的分享内容,主题为《字节跳动在 Rust 微服务方向的探索和实践》。
本次分享内容主要分为以下三个部分:
- 我们为什么选择了 Rust 语言;
- 我们做了什么;
- 展望未来:机遇与挑战。
嘉宾介绍
我会为大家主要介绍一下我们为什么会选择 Rust 语言,因为大家可能听说字节跳动比较有名的框架叫做 Kitex,是一个 Go 的框架,字节跳动在 Go 方向投入了很多,为何现在开始探索 Rust 方向呢?其次,在这个方向我们做了哪些事情,遇到的一些问题,以及我们的解决方案。其三,会为大家分享一下我们所认为目前 Rust 的机遇以及未来挑战。
我们为什么选择了 Rust 语言
一开始,我是做 Go 的语言开发,做 Go 的 RPC 框架,但当时我们遇到了很多 Go 语言的一些问题。
Go 语言的桎梏
- 深度优化困难
在 Go 里面,你想要深度优化是非常非常困难的一件事情,因为当我们体量变得越来越大的时候,深度优化是越来越重要了。但是如果在 Go 里面,想要去做一些深度优化,你经常会发现在跟 runtime 以及编译器做很多斗争,需要用一些很 hack 的办法去绕过它的一些限制。
- 工具链和包管理不够成熟
Go 里面的工具链和包管理相对来说不太成熟,如果有用过我们开源的 Kitex 框架的同学可能会非常了解。举个例子,比如在 Go 里面,想调一个 gRPC 的服务,或调一个 Thrift 的服务,需要调一个需要生成代码的服务,我需要先在开发的时候把代码生成好,要用一个命令行工具生成完之后,把生成代码一并地给提交到版本管理里面。直白来说,这是个很蠢的做法,像 C++、Java,还有 Python 也许都会采用一些其他的方案,但是 Go 就必须这么操作,因为它编译器就没有这个能力,没有办法在编译时去生成这个东西。还有一点,比如我在编译时也许可以调个脚本去生成,但问题在于本地没有这个文件,代码生成、代码补全提示都没有, IDE 会直接给你里面所有的就下划线飘红,这是个体验很差的事情。
- 抽象能力较弱
Go 里面的抽象能力是比较弱的,它没有零成本抽象这么个概念。
深度优化困难
我分享一个之前遇到的真实、有意思的案例。我们做序列化、反序列化的过程当中,可能会遇到一些出错的情况。在我们之前的版本里面,代码是很简单的,在序列化、反序列化出错的时候,就直接把 error 返回来。后来我们为了优化用户的体验,想多返回一些错误信息,比如我们告诉他是在哪个 struct 里面,在 read 哪一个字段的时候出的错。这是一个很好的初衷,但是当我们新的代码上线了之后,有一个业务方就跑来说:“你这个新的代码是不是有问题?为什么我们的性能下降了 20%?” 我们认为不可能,我们所有序列化、反序列化逻辑都没有改,只改了这一行。当时我们也很纳闷,认为是不是业务方自己测试环境有问题,后来我们查了半天,最后终于发现了情况。
新版本代码
旧版本代码
大家可以看到这个是新的代码生成出来的汇编,在 Go 里面它的汇编生成或编译器,应该说非常非常不智能,它没有做一些像代码位置上的调整,或者没有做这种代码的指令的重排,就导致了比如我们刚才看到的 error 的错误信息,他直接把所有的错误信息或这些字符串全部插入到了正常的流程当中。带来了什么问题?带来了我们的 L1 的 cache miss 的大量提高,因为 L1 的 cache 是会很大程度上影响到我们的执行性能、运行性能,所以就导致了性能的下降。
如何解决这个问题?你们可能会认为,编译器的问题是不是无法解决。后来我们用了个非常 hack 的办法,既然编译器不会去做代码重排,我们就只能自己做。我们自己把所有的错误都定义到了正常 return 语句的后面,在出错的时候用 Go to 跳转到后面,跳转到 label 上。大家可能在写代码或者学的时候都会听说过说 Goto 要慎用,尽量别用。但是在这种场景下,我们只能这么做。这个时候 Goto 语句被直接编译的时候,生成一条汇编的 jmp 指令,最后测出来的性能比之前的旧的版本即直接 return error 还要好。因为它的 cache 的 miss 直接从大概之前的 2.4 降到了现在的 1.8,又提升了很多。
这个是一个很有意思的例子,也体现出了在 Go 上面我们想要做深度优化其实非常的困难。
零成本抽象
还有一个就是在 Go 里面其实没有零成本抽象概念。零成本抽象的意思就是,如果我们没有用到的东西,是不需要为之付出代价的;而如果我们用到的东西,不管是编译器、标准库还是第三方库的提供者,都应该是做到最好的,不可能做得更好。可能写 C++,还有写 Rust 的同学比较了解这个概念,但是在 Go 里面是没有的。
Thrift 编解码抽象文章来源:https://www.toymoban.com/news/detail-455435.html
而为什么说 Go 里面没有零成本抽象概念,举个例子,我们做 Thrift 的编解码抽象,那么 Apache 官方的 Thrift,是支持很多种不同的 Transport 和 Protocol 组合。底层传输层,还有上层的序列化层,它其实是有很多不同的协议的文章来源地址https://www.toymoban.com/news/detail-455435.html
到了这里,关于字节跳动在 Rust 微服务方向的探索和实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!