Loading

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

2021-02-09 21:31:02 2262

乐鱼和卓英软件
作者 | Tom MacWright
译者 | 吴留坡
策划(huá) | 蔡芳芳
来源丨(shù)前端之巅(ID:frontshow)

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

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

但去(qù)年(nián),我试图用(yòng) Rust 写一(yī)个传统网站的纯 API 服务,Rust 就不合适(shì)了。

缺失很多小(xiǎo)功(gōng)能

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

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

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

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

Rust 就不(bú)这样,只有少许第(dì)三方库(kù),但(dàn)以这些服(fú)务的开发速度(dù),它们真的能够提(tí)供高质(zhì)量的体验吗?

有人会说好吧,X 编程语言太好(hǎo)了,你可以在周末自己写一(yī)个 SDK!我必(bì)须回答,不(bú)。

Rust 的生态(tài)系(xì)统在其它领域非常丰富。用于构建 CLI、管理并发性、使用二进制数据和底层解析器的 crates 令人印象深刻(kè),非常(cháng)棒。

Rust 编(biān)译器比(bǐ)以前(qián)快,但仍(réng)然很慢(màn)

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

但与其它编程语言相比,用它构建网站会很慢。它(tā)比编译型编程语言 Go 慢得(dé)多(duō),也比解释(shì)型编(biān)程(chéng)语言 JavaScript、Ruby 和 Python 等(děng)慢得(dé)多(duō)。

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

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

Rust 很复杂

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

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

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

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

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

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

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

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

相(xiàng)比较之(zhī)下,Python 的 Tornado 和 Twisted 异步实现的很奇怪,Node.js 异步实(shí)现的很(hěn)好(hǎo),但(dàn)语法(fǎ)都很丑陋。

我确信(xìn),Rust 的异步将会稳定和统一(yī),未(wèi)来(lái)会更容易操作(zuò),但我现在就要用啊。

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

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

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

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

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

这一部分不仅仅是 Rust,它(tā)还涉及 GraphQL 生态系统,Rust 参与这(zhè)个生态系统就(jiù)是一个(gè)例子。

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

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

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

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

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

一些注意事项

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

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

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

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

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

 延伸阅读(dú)

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


">

    乐鱼(中国)leyu

    乐鱼(中国)leyu