Loading

Rust不适(shì)合开发Web API

2021-02-09 21:31:02 2262

乐鱼和卓英软件(jiàn)
作者 | Tom MacWright
译(yì)者 | 吴(wú)留坡
策划(huá) | 蔡芳(fāng)芳
来源丨(shù)前端之(zhī)巅(diān)(ID:frontshow)

Rust 是一门神奇(qí)的编(biān)程(chéng)语(yǔ)言,有非常好的 CLI 工具,比如 ripgrep 和 exa。像 Cloudflare 这样的(de)公(gōng)司正(zhèng)在使用并 鼓励人(rén)们写 Rust 来运行微(wēi)服务。Rust 编写的软件可能比(bǐ) C++ 或(huò) C 更安全、更小、更简(jiǎn)洁。

如果我正(zhèng)在编写(xiě)一个地(dì)理编码器、一个路由(yóu)引擎、一个实(shí)时消息平台(tái)、一个数(shù)据(jù)库或一个 CLI 工具,Rust 最合(hé)适。

但去年,我试图用(yòng) Rust 写(xiě)一个(gè)传(chuán)统网站的(de)纯(chún) API 服务,Rust 就不(bú)合适了。

缺失很多小功能

Rust 有大(dà)量的(de) Web 服务框架、数据库连接(jiē)器和解析(xī)器。但搭建身(shēn)份验证服(fú)务方面只有非常低层(céng)次的组(zǔ)件(jiàn)。Node.js 有 passport.js,Rails 有 devise,Django 有 开箱即(jí)用的(de)身份验证模(mó)型,在 Rust 中,你需要(yào)学习如何将共(gòng)享 Vec 转换到底层加密库才能构建这个系统。

译者注(zhù),Vec 是一(yī)个动(dòng)态(tài)数组,只会自动增长而不会自动收缩(suō)。区(qū)别于 Array,Vec 具有动态的添加和删除元素的能(néng)力,并且能够以 O(1) 的(de)效率进行随(suí)机访问。Vec 的所有(yǒu)内容项都(dōu)是生成在(zài)堆空间上的,可以轻易的(de)将 Vec 移出一(yī)个栈而不用担心内存拷贝(bèi)影响执行效率,毕竟只是拷(kǎo)贝栈上的指针(zhēn)。

有些库试图解决这(zhè)个问题(tí),比(bǐ)如 libreauth,但它才刚刚开始开发。还有很多类似的 Web 框架问题。

SDK 呢?在主流(liú)编程语言中,你可以通过一个官方库来接入 Google 云服务(wù)、AWS 或(huò) Stripe。这些官方库大都很棒。例如,aws-sdk-js 和(hé) Stripe 库的设计和维护得非常好。

Rust 就不这样,只有(yǒu)少许第三方(fāng)库,但以这些服务的开发速(sù)度,它(tā)们真的能够(gòu)提(tí)供高质量(liàng)的(de)体验吗?

有(yǒu)人会说(shuō)好吧(ba),X 编程语言(yán)太好了(le),你可(kě)以在周末自己写一个(gè) SDK!我(wǒ)必(bì)须回答,不(bú)。

Rust 的生态系统在其(qí)它领域非常丰富。用于构建(jiàn) CLI、管理并(bìng)发性(xìng)、使用二(èr)进制数(shù)据(jù)和底层解析器的 crates 令人印象深刻,非常棒。

Rust 编译器比以前快,但(dàn)仍然很慢

我一直在看 Nicholas Nethercote 的博客,描述了 Rust 团队如何优化编译器,让它更快!

但(dàn)与其它编(biān)程语言相(xiàng)比(bǐ),用(yòng)它构建网(wǎng)站(zhàn)会(huì)很慢。它比编译(yì)型编程语言 Go 慢(màn)得多(duō),也比解释型编程语言 JavaScript、Ruby 和(hé) Python 等慢(màn)得多。

一旦(dàn)代码被编译,一切(qiē)就变得非常棒了!但在我的情况(kuàng)下,甚(shèn)至基本 API 功能都不完整,一(yī)个不(bú)复杂的系统——居然花了 10 多分钟(zhōng)来编译。Google 代码构(gòu)建(jiàn) 的硬件(jiàn)配置很差,每次都会(huì)超时(shí),我啥都编译不了。

只要不重建缓存依(yī)赖项,缓存就有意(yì)义。也许 减少依赖 会加快 Rust 项目编译。但就像 serde,几乎所有人都使用的 JSON 和其它序列化 / 反序(xù)列化程序占用了大量的(de)编译时间(jiān)。我们(men)是(shì)否应该用编译速度(dù)更快但(dàn)缺(quē)乏大(dà)量文档和生态系统支(zhī)持的东(dōng)西来取代 serde?这种(zhǒng)取(qǔ)舍非常(cháng)糟糕(gāo)。

Rust 很复杂

Rust 让你从(cóng)代码维度进(jìn)行思考(kǎo),这对系统(tǒng)编程来说(shuō)非常(cháng)重要。它让(ràng)你(nǐ)思考如(rú)何共享或复制内存,思考真实但不太可(kě)能的小概率事件,并确(què)保妥善处理它们,帮你编写各(gè)种各样的高效代码。

这些担(dān)忧都(dōu)是合理的,但是(shì)对于大多(duō)数 Web 应用程序(xù)来说,它们并不是最重要(yào)的关注(zhù)点,以流(liú)行的惯性思考会导致不正确的假设。

就拿 Rust 的安全性来说(shuō)吧。这(zhè)是它宣传语中的(de)重要(yào)部(bù)分,这(zhè)是绝对正确(què)的:Rust 的承(chéng)诺安(ān)全和底层两者兼而有之——它可以在(zài)没有(yǒu)垃圾收集器的情况下工(gōng)作,同(tóng)时防止基于内存的漏(lòu)洞。当你读到“安全”的时候,想(xiǎng)想 Rust 的竞争对手 C 吧。C 语言中的代码可以引用(yòng)任意内存,很容易溢(yì)出和出错。Rust 代码(mǎ)可(kě)以和 C 代码一(yī)样快(kuài),但是可以(yǐ)保护内(nèi)存(cún)访问,而不需要垃圾收集器或某种运行时检查。

但是 Rust 的内存规则(zé)并不(bú)比 Node.js 或(huò) Python 更(gèng)安(ān)全(quán),用 Rust 编写的 Web 应用(yòng)程(chéng)序(xù)在系(xì)统(tǒng)上不会比 Python 或 Ruby 应用程(chéng)序安(ān)全。带有垃(lā)圾收集器(qì)的高级编程(chéng)语(yǔ)言(yán)通常为避免这(zhè)类漏(lòu)洞利用和(hé)错(cuò)误而付(fù)出(chū)性能损失。不能在(zài) JavaScript 中引用未(wèi)初始化的内存(cún),因为 JavaScript 中不进行内存间的引用。

旁注:这是(shì)在描(miáo)述 Node.js 和其它系统的(de)设计目标——它们确(què)实偶尔(ěr)会有 bug。Node.js 的缓存对象,就值(zhí)得读一读。

你要是 问一些人,他们会说如(rú)果使用不(bú)安全的(de)代码(mǎ),Rust 相比(bǐ)带有内存回收的编程语(yǔ)言(yán)是不安全(quán)的——包括最流行(háng)的 Web 框(kuàng)架 Actix(译者(zhě)注,Actix 是 Rust 的 Actor 异步(bù)并发框架,基于 Tokio 和 Future,开箱具(jù)有异步非(fēi)阻塞事件驱动并发能力,其实(shí)现低层级 Actor 模型来(lái)提供无锁并发模型,而且同时提供同(tóng)步 Actor,具有(yǒu)快(kuài)速、可靠,易可(kě)扩展 https://actix.rs/),因(yīn)为 不安全代码允许原始指针的延迟。

如果你正在写(xiě)一个(gè)视频游(yóu)戏(xì),暂(zàn)停执行垃圾收集是不好的。如果你在编写(xiě)微控制器代码,任何内存“开(kāi)销”或浪(làng)费都(dōu)是非(fēi)常糟糕的。但是(shì)大多数(shù) Web 应用(yòng)程序可以节省一点内存开销来换(huàn)取(qǔ)生产性能(néng)。

Rust 的其它属性面对(duì)的争议几(jǐ)乎(hū)一样。它的并发特性是太神(shén)奇了,如果你在做一(yī)些复杂的(de)事情,需要(yào)快(kuài)速响应,这当然很棒。但如(rú)果情(qíng)况不是这样呢?至(zhì)少可以说,Rust 的异步(bù)生(shēng)态系(xì)统面临(lín)着很大挑战:各种(zhǒng)不相关的领域中有着不同的异步实现,比如 tokio。

相比(bǐ)较之下,Python 的 Tornado 和 Twisted 异(yì)步(bù)实现的很奇怪,Node.js 异(yì)步实现的很好,但(dàn)语(yǔ)法都很丑陋。

我确信(xìn),Rust 的异步将会稳(wěn)定(dìng)和统一,未来会更容易(yì)操作,但我现在就要用啊。

Rust 生态系统不是以(yǐ) Web 为中心的

很多人正在学 Rust,用 Rust 编写(xiě) CLI 应(yīng)用程序或(huò)底层(céng)代码,并(bìng)且(qiě)玩得非(fēi)常开心。使用 Rust 编写普通(tōng) Web 应用程(chéng)序的人明显少很多。

这(zhè)是技术选(xuǎn)择中的重要部(bù)分(fèn):是否有人在使用该工具?他们大致在(zài)同一个(gè)领域吗?不幸的是(shì),Rust 生态系(xì)统中许多令人难以置信的令人(rén)兴奋的工作与 Web 应用服务器无关。的确存在一些很有(yǒu)前途的 Web 框架——甚至更(gèng)高层次的框架,但毫(háo)无疑(yí)问,它们市场很小。即使是(shì)主要(yào)的 Web 框架(jià) Actix 也只有几个顶尖(jiān)贡献者。

如(rú)果 Rust 以目(mù)前的速度(dù)增长,那么社区中的 Web 部(bù)分将(jiāng)达到(dào)一(yī)个(gè)临界值,但我认为没有足(zú)够多的(de)人使用(yòng) Rust 作为(wéi)网站的实用(yòng)工(gōng)具。与其它社区(qū)相比,有(yǒu)很多公(gōng)司致(zhì)力于(yú)使用现有的工具(jù)来构建 Web 应用程序(xù),这些(xiē)工具(jù)不是最前沿的(de),但足够将成熟(shú)技术与(yǔ)新技术(shù)区(qū)分开(kāi)来(lái)。

Juniper 的 N+1 次查询(xún)

这一部分不仅(jǐn)仅(jǐn)是 Rust,它还(hái)涉(shè)及 GraphQL 生态(tài)系统,Rust 参与这个生态系统就是一个例子。

N+1 问题 是每个(gè)构建 Web 应(yīng)用程序的人(rén)都应该知道的。要点是:你有一页照片(一次查询),你要(yào)显(xiǎn)示(shì)每张(zhāng)照片的作者,会有多少次查询:1,合并照片和(hé)作者,或者在(zài)检索照片后对每(měi)张(zhāng)照(zhào)片进行查询(xún)以获取作者(zhě)?或者两次(cì),第二次查询(xún) ids 中的 user.id,一次获取所(suǒ)有作者,然后重新设(shè)置(zhì)他(tā)们(men)的照片属性。

N+1 查询通常优先使(shǐ)用数据库(kù)解决:比如(rú)将 N+1 查询(xún)改为单个查询,会带来(lái)明(míng)显(xiǎn)的(de)性能优化。我们有很(hěn)多方法来尝试和解决这些问题:你(nǐ)可以编写 SQL,并(bìng)尝试使用 CTE 和 JOIN 在(zài)单个查询(xún)中(zhōng)完成大量(liàng)工作,就(jiù)像我们(men)在 Observable 中(zhōng)所做的那样,或者使用像 ActiveRecord 这样的 ORM 层将 N+1 查询转换为可预测查询的快速方法。

Juniper 是一个(gè)用(yòng)于 Rust 应用(yòng)程序的 GraphQL 服务。GraphQL 基本上都是由前端应用(yòng)程(chéng)序定(dìng)义查询,而不是后端。给它一(yī)系列可以查询的东西,然后应用程序(React 或(huò)其它)将任意查询发送到后(hòu)端。

这(zhè)会让后端变得复杂。任何 SQL 级别的优(yōu)化都不可能(néng)做到——你的服务器正在(zài)编写动态 SQL,优化只能(néng)依赖 GraphQL 服务,但它不会总是有效。例(lì)如:Juniper 默认情况下执行(háng)的是 N+1 查询,解决方(fāng)案 dataloader 还比较(jiào)粗糙且(qiě)需要单独维护。因此,最终您(nín)将拥有一(yī)个非常(cháng)快的应(yīng)用程序层,但它所有的时间都花在了极其低效的数据库查询上。

总之,GraphQL 与(yǔ) NoSQL 数据库配合使(shǐ)用效果非常好,它可以快速为这(zhè)些类(lèi)型的请求提供服务。我(wǒ)确信 Facebook 内部有一(yī)些特定的数据库(kù)与 GraphQL 结合在一起使用效果非(fēi)常棒,但业内其他企业则(zé)非(fēi)常依赖 Postgres 和同类(lèi)产品。

一些注意事(shì)项(xiàng)

首先,本文提到的问题并不针对在通用场景使用 Rust,只针对将 Rust 用于特定目标(biāo)和生态系统,简单说(shuō)就(jiù)是 Web API。

注意事项 1:一般情况下(xià),你可(kě)以用任何编程语(yǔ)言搭建网站(zhàn),还记得基于 C++ 实现的OkCupid 吗(ma)?(译者注,OkCupid 是美国一个(gè)大型线上交友网站)还有(yǒu)一个非常流行的 星象应用程序,Co-star,它全部是用 Haskell 编(biān)写的。如(rú)果你擅长其它编程(chéng)语言,或者可以招聘(pìn)到擅长这些编程(chéng)语言的工(gōng)程师(shī),你一样可以取得(dé)成功(gōng)。

注意事项(xiàng) 2:我试图构建(jiàn)的是重 CRUD(增删改(gǎi)查) 的 Web 应用程序 API。它可能不算是一个 Web“服(fú)务”——主要是快速、无数(shù)次地执行同一(yī)个操作,而是(shì)一个 Web“应用(yòng)程(chéng)序”——执(zhí)行了许多不同的操作,包(bāo)含了相当多的业务逻辑。如(rú)果你要(yào)开发的(de)东西跟我在做的(de)不一样,那(nà)我的建议可(kě)能(néng)就不适合你(nǐ)。如果你(nǐ)需要的是快速(sù)执行一两(liǎng)个操作,比如你正在(zài)写一个支付(fù)网关(guān)或语音消息应用程序,那(nà) Rust 可能(néng)效果还(hái)是不错的。

注意事项 3:这篇文章写(xiě)于 2021 年(nián) 1 月(yuè),如果接下来社(shè)区继续发(fā)展,Rust 将得到持续的改进,会变得更好并更易于 Web 应用(yòng)程序开发。

总而言之,我真的很喜欢使用 Rust,这是一门美丽的编程语言,有(yǒu)很多很酷的(de)想法。希望很快,Rust 会成为能用来(lái)构建(jiàn)我想做的东西(xī)的(de)最合适的工具(jù)。不(bú)过,现在(zài)我想做的很多东西都要采用不同特(tè)性的编程语(yǔ)言才能更(gèng)好地(dì)运行(háng)。

 延伸阅读(dú)

https://macwright.com/2021/01/15/rust.html


">

    乐鱼

    乐鱼