Loading

Rust不适合开发Web API

2021-02-09 21:31:02 2263

华体平台和卓英软件
作者 | Tom MacWright
译者 | 吴留坡
策划 | 蔡芳芳
来源(yuán)丨前端(duān)之巅(ID:frontshow)

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

如果我(wǒ)正在编写一个地理编码器、一个路由引擎、一个实(shí)时消息平台、一个(gè)数(shù)据库或一个(gè) CLI 工具,Rust 最合适。

但去年,我试图(tú)用 Rust 写一个传(chuán)统网(wǎng)站的纯(chún) API 服(fú)务,Rust 就(jiù)不合适了。

缺(quē)失很多小功能(néng)

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

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

有些(xiē)库试图解决(jué)这个问题,比如 libreauth,但它才刚刚开(kāi)始开发。还有很多类似的 Web 框架(jià)问题(tí)。

SDK 呢?在主流编(biān)程语言中(zhōng),你可以通过一个官方库来接入 Google 云服务、AWS 或 Stripe。这(zhè)些官方库大(dà)都很棒。例如,aws-sdk-js 和 Stripe 库的设(shè)计和维护得非常好。

Rust 就不这样,只(zhī)有少许第三方库,但以这些服务的开(kāi)发速度,它们真的能(néng)够提供高质量的体验吗(ma)?

有(yǒu)人会说好吧,X 编程语言太好(hǎo)了,你可以在周(zhōu)末自(zì)己(jǐ)写一(yī)个 SDK!我必须回答(dá),不。

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

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

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

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

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

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

Rust 很复杂

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

这些担忧都是合(hé)理(lǐ)的,但是对于大(dà)多数 Web 应用程(chéng)序来说,它们并不(bú)是最重要的关(guān)注点(diǎn),以流行的惯性思考会(huì)导致不(bú)正确的假设。

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

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

旁注:这是在描述(shù) Node.js 和其(qí)它系统的设计目标——它们确实偶尔(ěr)会有(yǒu) bug。Node.js 的缓存对象,就(jiù)值得读一(yī)读(dú)。

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

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

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

相比较之(zhī)下(xià),Python 的 Tornado 和 Twisted 异(yì)步(bù)实现的(de)很(hěn)奇怪(guài),Node.js 异步实现的(de)很好,但语法都很丑陋(lòu)。

我确信,Rust 的(de)异步将会稳定和统(tǒng)一,未来会更(gèng)容易操作,但我现在就(jiù)要(yào)用啊。

Rust 生态系统不是(shì)以 Web 为中(zhōng)心的

很多人正(zhèng)在(zài)学 Rust,用 Rust 编写 CLI 应用程序或底层代码,并且玩得非常(cháng)开心。使用 Rust 编写普通 Web 应用程序的人明(míng)显少很多。

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

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

Juniper 的(de) N+1 次查询

这一部分不仅仅是(shì) Rust,它还(hái)涉(shè)及 GraphQL 生态系统,Rust 参(cān)与这个生态系(xì)统(tǒng)就是(shì)一个例子。

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

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

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

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

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

一些注意事项

首先,本文提到的问题(tí)并不(bú)针对(duì)在通用场景使用 Rust,只针对将 Rust 用(yòng)于特定目标和生(shēng)态系统,简(jiǎn)单说就是(shì) Web API。

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

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

注意事项 3:这篇文(wén)章写于(yú) 2021 年(nián) 1 月,如果接下来社区(qū)继续发展,Rust 将得到持续的改(gǎi)进,会变(biàn)得更(gèng)好并更(gèng)易于 Web 应用程序开发。

总而言之,我真的很喜(xǐ)欢使用 Rust,这是(shì)一门(mén)美丽(lì)的(de)编程(chéng)语言,有很多很酷(kù)的想法。希望很(hěn)快,Rust 会(huì)成为能用(yòng)来构建我想(xiǎng)做的东西的最合适的工具。不过,现在我想做的很多东(dōng)西(xī)都要采用不同特性的编程(chéng)语言才(cái)能更好地运行。

 延伸(shēn)阅读(dú)

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


">

    华体平台-华体(中国)

    华体平台-华体(中国)