Loading

Rust不适合开发Web API

2021-02-09 21:31:02 2263

九游官方端入口和卓英软(ruǎn)件
作者(zhě) | Tom MacWright
译者 | 吴留坡
策划 | 蔡芳芳
来源丨前端之巅(ID:frontshow)

Rust 是(shì)一(yī)门神(shén)奇的编程语言(yán),有非常好的 CLI 工具,比如 ripgrep 和 exa。像 Cloudflare 这样的公司正在使用并 鼓励(lì)人们(men)写 Rust 来运行微服务。Rust 编(biān)写的软(ruǎn)件可能比 C++ 或 C 更安全、更小、更简洁。

如果我(wǒ)正在编写一(yī)个地理编码器、一个路(lù)由引擎(qíng)、一(yī)个实时消息平台(tái)、一个数据(jù)库或一个 CLI 工具,Rust 最(zuì)合适(shì)。

但去年,我(wǒ)试(shì)图(tú)用 Rust 写一个传统网站的(de)纯(chún) API 服务(wù),Rust 就不合适了。

缺(quē)失很多(duō)小功能

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

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

有些库试图解决(jué)这个问(wèn)题,比如(rú) libreauth,但它才刚刚(gāng)开(kāi)始(shǐ)开发。还有很多类似的 Web 框架问题。

SDK 呢?在主流(liú)编程语言中,你可以通过(guò)一个官(guān)方库来接入 Google 云服务、AWS 或 Stripe。这些(xiē)官方(fāng)库大(dà)都很棒。例(lì)如,aws-sdk-js 和 Stripe 库的设计和维护得非常(cháng)好。

Rust 就不这样(yàng),只有少(shǎo)许(xǔ)第三方(fāng)库,但以(yǐ)这(zhè)些服务的开发速(sù)度,它们(men)真的能够提(tí)供(gòng)高质(zhì)量的体验吗?

有人会说好吧,X 编程语言太好了(le),你(nǐ)可以在周末(mò)自己写一个 SDK!我必须回答,不(bú)。

Rust 的生态系统在其它领(lǐng)域非常丰(fēng)富。用于构建 CLI、管理并发性、使用二进制数(shù)据和底层解析器的 crates 令人(rén)印象深刻,非常棒。

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

我一直(zhí)在看 Nicholas Nethercote 的博客,描述了(le) Rust 团队(duì)如何优化(huà)编译器,让它更(gèng)快!

但与(yǔ)其它编程语言相(xiàng)比(bǐ),用它构(gòu)建网站(zhàn)会(huì)很(hěn)慢。它比编译型(xíng)编(biān)程语(yǔ)言 Go 慢得多,也比解释型(xíng)编程(chéng)语言(yán) JavaScript、Ruby 和 Python 等慢得多。

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

只要不重建缓存依赖项,缓存就有意义。也许 减少(shǎo)依赖(lài) 会加快(kuài) Rust 项目编译。但就像 serde,几(jǐ)乎所有人都(dōu)使用的 JSON 和其它(tā)序列化 / 反序列化程序占用了大量的编译时间。我们是否应该用编译速度更快但缺乏大量文档和生态系统支持的东(dōng)西来取代 serde?这种取舍非(fēi)常糟(zāo)糕。

Rust 很(hěn)复杂

Rust 让(ràng)你从代码维度(dù)进行思考,这对系统编程(chéng)来说非(fēi)常重要。它让你思考如何共(gòng)享(xiǎng)或(huò)复制内存,思考真实但不太(tài)可能的小概率事件,并确保妥善(shàn)处理(lǐ)它们,帮你(nǐ)编写各种(zhǒng)各样的高效代码。

这些担忧都是合(hé)理的,但是对于大多数(shù) Web 应用(yòng)程序来说,它们并不是最重要的(de)关注(zhù)点,以流行的惯性(xìng)思考会导致不正确的假设。

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

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

旁注:这是在描述 Node.js 和(hé)其它(tā)系统的设计目标——它(tā)们确实偶尔(ěr)会(huì)有 bug。Node.js 的缓存(cún)对象,就(jiù)值(zhí)得读一(yī)读。

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

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

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

相比较(jiào)之下,Python 的 Tornado 和 Twisted 异(yì)步实现的(de)很奇(qí)怪,Node.js 异步实现(xiàn)的很好,但语(yǔ)法都很丑陋。

我确信,Rust 的异(yì)步(bù)将会稳定(dìng)和统一(yī),未来会更(gèng)容易操作,但我现在就要用啊(ā)。

Rust 生态(tài)系统不是以 Web 为中心(xīn)的

很(hěn)多人正在学(xué) Rust,用 Rust 编写(xiě) CLI 应用程序或底层(céng)代码,并且玩得非常开心。使用 Rust 编写普通 Web 应用(yòng)程(chéng)序的人明显少(shǎo)很多。

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

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

Juniper 的 N+1 次(cì)查询

这一部(bù)分不仅仅是 Rust,它还涉(shè)及(jí) GraphQL 生(shēng)态系统,Rust 参与这个生态系统就是一个例子。

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

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

Juniper 是一个用于(yú) Rust 应(yīng)用程序的 GraphQL 服(fú)务。GraphQL 基本上都是(shì)由前端应用程序定义查询,而不是后端(duān)。给它一系列可以(yǐ)查(chá)询的东西,然后应用程序(React 或其它)将任意查(chá)询发送到后端。

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

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

一些注意事项(xiàng)

首先(xiān),本文提到的问题并(bìng)不针对在通用场景使用 Rust,只针对(duì)将 Rust 用于特(tè)定(dìng)目(mù)标和生态系统,简单说就是 Web API。

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

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

注意事(shì)项(xiàng) 3:这篇文章写于(yú) 2021 年 1 月,如果接下来(lái)社区继续发展,Rust 将得到持续的(de)改进,会变得(dé)更(gèng)好(hǎo)并更易(yì)于 Web 应用程序(xù)开发。

总而(ér)言之,我真的很喜欢使用 Rust,这是一门美丽的编程语言,有很多很酷(kù)的(de)想法。希望很快(kuài),Rust 会成为(wéi)能用来构建我(wǒ)想做的东西的最合适(shì)的工具。不(bú)过(guò),现在我(wǒ)想做的很多东西都要(yào)采(cǎi)用不同特性的编程语言才能更好地运行。

 延伸阅读

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


">

    九游官方端入口-九游(中国)

    九游官方端入口-九游(中国)