两年多来,Kraken的Core Backend团队一直在使用Rust来对最初用PHP编写的服务进行现代化,同时开发新产品,扩展功能集并支持加密货币交易活动的不断增长。
嗨👋!我是Kraken的工程总监Simon,领导核心后端团队。我想回顾一下Core Backend团队在过去两年中对Rust的使用情况,并与我们一起大规模地分享我们的观点。有许多很棒的在线资源介绍了Rust的不同之处以及为什么它是一门伟大的语言。但是,这不是这样的博客文章。我们希望本文将对考虑使用Rust进行开发的公司以及想要投入时间来学习该语言的开发人员有所帮助。最后,这也非常感谢所有帮助使Rust成为可能的人,也是Kraken为这一运动做出积极贡献的一种方式。
通常,从头开始构建解决方案来替代一个问题会给我们带来另一个问题。当原始开发人员不参与新解决方案的设计和实现时,这种情况尤其常见。有时,新的解决方案从理论上讲会更好,但是要准备好太多的时间,这会减慢主动服务请求的系统的进度。尽管我们可以确保避免这些常见的陷阱,但是首先挑战进行重写的需求非常重要。
Kraken于2011年成立时,PHP提供了执行安全性,速度和生产率的完美结合。令人印象深刻的是在早期建立了多少功能。但是,从那以后,Kraken取得了长足的发展,并且很难扩展PHP代码库,共享专有技术并安全地进行较大的更改。这些核心服务处理分布式数据存储,加密和信息安全性方面的考虑,这在PHP开发人员中不太可能是常见的技能,而PHP开发人员通常更专注于在现有Web和电子商务框架上进行构建。
更普遍的是,Kraken进入了高速增长的阶段,在此阶段,代码库和工具都需要跟上发展。在这方面,动态类型的编程语言非常适合开始构建,但是随着代码库和工程师人数的增加,维护代码变得更加困难。强类型提供了保证(和正式文档),这些保证使快速开发和更多开发人员可以在单个代码库上工作。
人们在2018年初就认识到,保留PHP并不是实现这些目标的最佳长期解决方案。
2018年初,Kraken已经提供了用Go和C ++编写的生产服务。尽管Rust保证了性能,安全性和现代语言的结构,但将其作为重写核心服务的语言是一个赌注。
Kraken非常注重安全性。因此,我们不想让C ++代码针对用户输入运行。即使是世界上最好的C ++团队(例如构建Windows™或Chrome™的团队),也会在代码中产生约70%的CVE是由于内存安全性问题而导致的-诸如释放后使用,缓冲区溢出,双重释放等会导致特权Java,Go或Rust等语言完全禁止升级或访问内存。
尽管Go可以防御此类特定的漏洞,但它没有提供诸如通用或求和类型之类的现代编程功能,这些功能最终会导致数据建模问题或重复。 Kotlin提供了一个更复杂的类型系统,并且像Go一样,它使异步编程相对容易,但是带有一个带有许多遗留物的Java生态系统。
输入Rust。它的可靠性和性能使其在加密货币和区块链项目中取得了成功。一些Kraken工程师开始进行试验,并视其为构建满足Kraken后端需求的持久系统的机会:类似于C ++的性能,现代语言构造有助于准确地建模业务逻辑和错误情况,计划的对异步编程的一流支持,编译时线程安全和充满活力的生态系统。 Rust的价值主张和社区取得的成功使Kraken开始在2018年中开始重写Rust的核心服务。
Core Backend团队已经成长,如今负责现代的Rust核心服务和仍在重写的遗留PHP服务。同时,Rust已被其他团队成功使用:Kraken Futures团队加入了我们的行列,该团队独立地开始将其所有后端堆栈迁移到Rust,Cryptowatch选择了Rust作为其桌面应用程序,Kraken迁移了其Rust的冷库系统,Rust正在建立Kraken Digital Asset Bank。该语言本身已得到显着改进,从而使编写异步网络服务比以往更加容易。
总的来说,我们一直很忙:Core Backend团队的Rust git存储库保存了约500,000行代码,比PHP还要多,尽管许多功能仍在PHP中实现。这部分是由于编写了更多的基础代码,测试和全新功能,而且还因为PHP与其他动态类型化的编程语言一样,不需要键入结构定义(包括错误),而该结构定义弥补了错误的大部分。 Rust代码。在PHP中没有那些显式的结构使得重写几乎是逆向工程中的一种练习。
从策略上讲,我们决定重写Rust中完全相同的功能:由于所有PHP服务都是无状态的,因此可以轻松地将逻辑(逐个端点)移植到Rust。这样一来,新聘的团队就可以获得有关底层系统的更多知识,并可以进行增量部署以及轻松回滚。我们已经构建了一个全面的集成测试套件,该套件需要针对PHP和Rust服务进行传递,以确保行为相似。将功能移植到Rust后,可以更轻松,更安全地进行扩展。
尽管重写的主要目标不是性能,但很高兴看到Rust提供开箱即用的惊人速度。我们的Tokio驱动的RPC服务器没有进行特别优化(尽管我们通常对内存使用模式非常谨慎),每个实例支持150k请求/秒的吞吐量,同时将p99.9延迟保持在3ms以下。系统的运行速度与其最慢的部分一样快,虽然我们的PHP核心服务并不是Kraken的唯一瓶颈,但它们的IO性能要比Rust的低,并且对负载更敏感。在将整个端到端路径迁移到Rust并消除瓶颈之后,我们的客户应该会看到巨大的进步。同时,我们通过将端点移至Rust,重新设计数据库和扩展服务来尽一切努力来提高性能和可靠性。
Rust经常被吹捧为理想地适用于级别任务,命令行公用事业和网络服务(如负载均衡器)的伟大系统编程语言。许多人认为Rust的复杂性是一般业务逻辑的交易破坏者,并且工作市场池太小,无法使用语言进行常见任务,例如构建用户管理系统或REST API。
虽然生锈非常适合系统编程,但我们也一直在使用它用于常用语言中的应用服务,被认为是更高级别的诸如Java,Ruby或CypeScript。正确性在克拉肯绝对关键,Rust的现代语言构造使得更容易写正确,强大的代码。缺乏垃圾收集,经常被提升为写作一般逻辑,不需要“关心”记忆管理,在实践中并不是一个问题,因为我们正在构建无星级服务并存储循环数据永远不会是一个问题。
然而,RURT需要精确,我会说这是语言最有益的方面:它的明确性,由其强大的系统支持,导致表现力的代码,在运行时很容易审查和可靠。在这方面,我考虑生锈比Java和其他语言的较低级别和更高级别。核心后端团队还开发了一些技术服务,如负载均衡器或服务监控流,需要良好的性能,并且它非常实用,不必在系统和应用程序逻辑语言之间切换,并重用库和模式。
随着团队和代码基础的增长,有效审核代码的能力至关重要。 Rust非常显着,使行为非常明确,我的意思是没有必要思考系统的其他部分 - 当前功能通常足够。查看代码时,我们呈现出差异(已更改的线路和周围上下文),虽然人们可能需要更多的时间挖掘更改,更快的评论是可以快速获得反馈的开发人员的伟大动机。在Rust中,我可以确定编译的更改将是免费的数据比赛(并发突出的根本原因)和内存安全(我们仍然是安全生锈的大部分时间)。我可以轻松发现可能导致恐慌 - 生锈的中止执行方式的功能,当没有替代方案时,现货无用的内存副本和一般收集开发人员的意图。 Clippy,Rust的Linter,有助于统一代码风格,并导致更加惯用的,一致的代码库。我在过去两年中审查了数千个合并要求,比任何其他主流编程语言更高的置信度。
Rust是一种大,复杂的语言,很容易迷失细节。幸运的是,无需了解所有微小的细节以效率。我们的经验是铁锈是一种极其富有成效的语言:它有很好的工具,迫使我们彻底模拟问题,节省珍贵的调试时间和潜在的产量问题,并且对于代码重用而言,生产力倍增器很棒。最后,我觉得有必要揭穿“战斗借书”传说,一个故事描绘了博彩代理人的逃避编译器:在我的经验中,它大多是初学者,1%试图微优化代码或推动边界。大多数经验丰富的Rust开发人员都知道如何以一种方式模拟代码,即任何时候没有浪费在设计问题上的编译器,并且可以一目了然地发现反模式,就像大多数人都知道如何在正确的一面驾驶他们的汽车避免事故的道路,并注意那些没有的人!
在这两年内,我们已经建立了现代克拉肯后端堆栈的基础,重写了现有的功能来生锈,建立新的Rust服务和功能,并建立了30+工程师的核心后端团队。一些开发人员最初被聘为PHP开发人员,并在加入团队时学会了生锈。值得一提的是,克朗登是一家全球分布式遥控公司,核心后端球队拥有从12个国家工作的15个国籍的工程师。
Rust吸引了激情的开发人员,通常对系统编程,分布式系统或密码学感兴趣。我们目前的核心后端工程师的一部分是通过各种铁锈在线资源发现机会的铁锈爱好者,从Reddit到本周,我们的工作优惠很多次(谢谢!),使克拉肯闻名于招聘铁锈开发人员很长一段时间。
我们的核心后端角色将竞争激烈的市场中充满挑战的技术和业务问题结合在一起,在世界各地进行远程工作,并提供与位置无关的高额基本报酬和慷慨的选择权,并几乎全职撰写Rust。对于申请了这两年的许多候选人并没有失去这一事实,这使得组建世界一流的工程团队成为可能。
当我们最初对感兴趣的招聘开发人员敞开大门,但是对Rust却没有实际经验时,我们很快意识到它并不总是成功,学习曲线取决于个人。有趣的是,Rust吸引了来自非常不同的静态和动态类型语言的开发人员。很难说那些来自特定背景的人的学习曲线是否更难,但有些在几周内就可以生效,而另一些在数月后仍会挣扎。那些习惯于依赖文档并且对语义学有正式理解的人最有可能赶上。像许多快速发展的公司一样,我们需要新员工才能在实际问题上立即提供帮助。因此,我们需要可证明的Rust经验,并且需要进行测试,以便全面了解Rust的类型系统以及标准库和普通包装箱的实用知识。
我相信使用Rust可以帮助人们成为更好的开发人员,因为它推动了简洁的设计和精度。但是,仅了解Rust并不能使他成为一名出色的工程师。我们看到的许多候选人对Rust都很满意,但是在构建后端系统方面经验有限。我们雇用了许多具有巨大潜力的初级开发人员,因为在组建团队时,平衡是成功的关键。经验丰富的开发人员通常是出色的导师:他们通常具有使事情简单的智慧,学会了不要过于信任自己,以及如何最大限度地发挥其对业务的影响。
考虑到该语言如何解决C ++,Java或Go的痛点,我希望有更多经验丰富的开发人员能有所作为。我来自Java开发十多年,我对过度炒作的新技术表示怀疑,但是我现在害怕回到一种与Rust的品质不符的语言-特别是它如何让我专注于手头的模块,而不是需要不断考虑许多隐式不变式,例如该代码段是否从另一个线程调用,以及我需要使其成为线程安全的事实。我们希望,随着越来越多的公司现在从Discord和Deliveroo到亚马逊和微软more在Rust上进行大量投资,我们可以帮助发出一个信号,表明Rust的工作很多,并且花时间学习这种语言不会浪费。许多经验丰富的开发人员更愿意留在他们作为专家的堆栈上,但是有些人可能仍然喜欢尝试摆脱自己的舒适区并挑战自己。
Rust使我们能够构建许多运行良好的高性能生产代码。我们有一个庞大的团队,其中许多人在后端的非常不同的部分上工作。大多数代码都非常健壮:我们还没有经历过Rust核心服务的一次崩溃或恐慌(大致相当于运行时异常)。总而言之,我可以说我们仅遇到业务逻辑问题,配置错误问题,并且遇到了与在Musl libc上运行Tokio以及特定内核配置有关的一般性能问题,一旦使用perf工具确定了该问题,就可以轻松修复该问题。
虽然语言很棒,但也有一些被广泛认为是局限性的事情也困扰着我们。
理想情况下,每个易错函数都具有自己的错误枚举,以精确捕获其错误并进行处理,但实际上,它过于冗长,导致使用不太精确的错误特征或每个模块使用一个枚举。该语言可以更好地支持这一点:有许多倡议和宏对此进行了探索。
在设计库箱时,缺乏专业化和通用关联类型(GAT)可能会受到很大限制。
我们首先将Future Rust组合器与async Rust配合使用,并在每晚登陆时使用async / await支持。这是一项非凡的功能,使我们可以使用Tokio构建大规模的并发应用程序。我们从不需要花费很多时间来使我们的服务器处理超过10K的并发连接或实施背压。但是,它仍然可以变得更好:
与Rust的大多数部分不同,异步函数看起来有点像常规函数,但它们可能不会完全执行(更确切地说,它们返回的Future可能不会被轮询完成),因为它们看上去与常规函数无害。这需要格外小心,以处理清理逻辑,清理逻辑目前本身不能异步进行。正在进行的支持异步删除的工作有望提供一部分解决方案。如何使问题更明显仍是一个悬而未决的问题;属性可以清楚地表明Future是否可以安全取消,并且棉绒警告会告诉我们否则吗?
尽管情况越来越好,但异步框架的分裂已严重损害了生态系统。该语言将从提供的结构中受益匪浅,该结构将使任务调度子系统抽象而无需额外开销,因此人们可以选择使用自己喜欢的执行程序,并将任务执行程序传递给库,或者自行驱动Future。
静态初始化任务执行程序的当前设计使得通过简单地拉出依赖关系而容易地错误地运行多个执行程序。线程局部变量的普遍使用使调试更加困难。
能够在特征中设计异步函数而无需装箱,并能够引用结果类型,这绝对是对性能敏感的代码的一项重大改进。
我们也希望看到围绕ioing的工作有望带来巨大的性能改进,而又不会造成生态系统的进一步分裂。
在工具方面,Cargo和Rustup使得设置和编译项目变得无关紧要。 RustAnalyzer进行了惊人的改进,并提供了很棒的IDE体验,并且会变得更好。编译时间通常会减少:它们可能会更短,但是随着构建和sccache的增加,它们不会花费太多时间。优化的构建确实速度很慢,但是总体来说要付出很小的代价才能获得性能和安全性。具有许可支持的私人货运登记处一定会对Rust在企业中有所帮助。我们一直在使用git依赖项运行,但是缺少语义版本支持使更新很痛苦。那里有一些开源的Cargo注册中心,但是Cargo本身不支持访问令牌或凭证。我们很乐意赞助这项工作。
考虑到所有因素,Rust非常成熟,并且大多数痛点在其他主流语言中将以一种或另一种形式存在。 Rust使重用变得微不足道,并允许我们在主动开发下安全地处理大型代码库,而又不牺牲性能。
对我们而言,使用Rust不再是实验或赌注。这是我们正在建立的可靠技术,Core Backend团队正在寻求工程师的帮助。如果不提及Rust之外的团队价值观,这幅画是不完整的:Core Backend团队利用Rust的工程价值和受Netflix影响的高性能团队文化,扩展了Kraken的文化-以及对我们使命的承诺。我们相信超越代码的工程,拥有所有权。我们拒绝傲慢自大。我们不断相互学习。不共享办公室将使我们面临的挑战更加严峻,而蓬勃发展的个人则是工程师,他们能够自我驱动,技术精湛,能够在需求和技术解决方案之间架起桥梁,从而能够自主前进。我们将聪明和努力的工作结合在一起,但保持了良好的工作与生活平衡并保持健康。我们关心我们正在建设的东西,并帮助我们的队友取得成功。我们认识到完美是善的敌人(众所周知,铁锈开发人员是完美主义者!😉)。最后,我们相信团队会不断自我完善:如果技术或组织方面出现问题,我们会予以解决。
我们在核心后端团队中为中级和高级后端工程师以及有助于支持和改进的站点可靠性工程师提供了空缺
......