Loading

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

2021-02-09 21:31:02 2263

365上市公司和卓英软件(jiàn)
作者 | Tom MacWright
译者 | 吴留(liú)坡(pō)
策(cè)划(huá) | 蔡芳芳
来源丨前端之巅(ID:frontshow)

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

如(rú)果我正在编写一个(gè)地理编码器、一个路由引(yǐn)擎、一个实时(shí)消息平(píng)台、一(yī)个数据库或一个(gè) CLI 工具,Rust 最合适。

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

缺失很多小功能

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

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

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

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

Rust 就不这样,只有少许第三方库,但以这些服务的(de)开发速度,它们真的(de)能够提供高质(zhì)量(liàng)的(de)体验(yàn)吗?

有人(rén)会(huì)说好吧,X 编程(chéng)语言太好了,你可(kě)以在(zài)周末自己写一个 SDK!我必须回(huí)答,不。

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

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

我一直在看 Nicholas Nethercote 的博客,描(miáo)述(shù)了 Rust 团队如何优(yōu)化编译器,让(ràng)它更快!

但(dàn)与其它编程语言相比,用它构建网站会很慢。它比编译型(xíng)编程语言 Go 慢得多,也比解释(shì)型编程语言 JavaScript、Ruby 和 Python 等(děng)慢(màn)得多。

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

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

Rust 很(hěn)复杂(zá)

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

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

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

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

旁注(zhù):这是在描述(shù) Node.js 和其它(tā)系统的设计目标——它们确实偶尔会(huì)有(yǒu) bug。Node.js 的缓存对象,就值得读一读。

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

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

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

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

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

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

很多人正在学 Rust,用 Rust 编写 CLI 应用程序或底层代码,并且玩得非常开心。使用 Rust 编写普通 Web 应用(yòng)程序的人明显少很多。

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

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

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

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

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

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

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

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

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

一些注意事项

首先,本文提到(dào)的问(wèn)题并不针对在通(tōng)用场景使(shǐ)用(yòng) Rust,只针对将 Rust 用于特定目(mù)标和生态系统(tǒng),简单说(shuō)就是 Web API。

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

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

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

总而言之,我真的很喜(xǐ)欢使用 Rust,这是一门美丽的(de)编程(chéng)语言,有(yǒu)很多(duō)很酷的想法。希望(wàng)很快,Rust 会成为能用来构建我(wǒ)想做的东西(xī)的最合适的工(gōng)具。不(bú)过(guò),现(xiàn)在我想做的很多东西都要采用不同特性的编程语(yǔ)言(yán)才能更好地运行。

 延伸阅读(dú)

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


">

    365上市公司(英国)集团-官方网站

    365上市公司(英国)集团-官方网站