Loading

Rust不适合开发Web API

2021-02-09 21:31:02 2263

九游官方网页版和卓英软件
作者(zhě) | Tom MacWright
译者 | 吴留坡
策划 | 蔡(cài)芳芳
来(lái)源丨(shù)前端之(zhī)巅(ID:frontshow)

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

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

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

缺失很多小(xiǎo)功能(néng)

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

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

有些库试图解决这个(gè)问题,比如 libreauth,但它才刚刚开始(shǐ)开发。还有(yǒu)很多(duō)类似的 Web 框(kuàng)架(jià)问题。

SDK 呢(ne)?在主流编程(chéng)语言中(zhōng),你可以通过一(yī)个官方库(kù)来接入(rù) Google 云服(fú)务、AWS 或 Stripe。这些官方库大(dà)都很(hěn)棒。例如,aws-sdk-js 和(hé) Stripe 库的设计和(hé)维护得非常好(hǎo)。

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

有人会说好吧,X 编(biān)程语言(yán)太好了,你可以在周末自(zì)己写一个 SDK!我必须回答,不。

Rust 的生(shēng)态(tài)系统(tǒng)在其它领域非(fēi)常(cháng)丰富。用于(yú)构建(jiàn) CLI、管(guǎn)理并发(fā)性、使(shǐ)用二进制数据和底(dǐ)层解析(xī)器的(de) crates 令(lìng)人(rén)印(yìn)象深刻,非常棒(bàng)。

Rust 编译器(qì)比(bǐ)以前快,但仍然很慢

我(wǒ)一直(zhí)在看 Nicholas Nethercote 的(de)博客,描述了 Rust 团队如(rú)何优化编译(yì)器,让它更快!

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

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

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

Rust 很复杂

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

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

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

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

旁(páng)注:这是在描述 Node.js 和(hé)其它(tā)系统的(de)设(shè)计目标——它们确实偶尔会(huì)有 bug。Node.js 的缓存对象,就值得读(dú)一读(dú)。

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

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

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

相比较之下,Python 的(de) Tornado 和(hé) Twisted 异步实现的(de)很奇怪,Node.js 异步实现的很好,但语法(fǎ)都很丑陋。

我确信,Rust 的异步将(jiāng)会稳定和统一,未来会更容易操作,但我(wǒ)现在就要用(yòng)啊。

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

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

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

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

Juniper 的 N+1 次查询

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

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

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

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

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

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

一些注意(yì)事项

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

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

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

注意事项 3:这篇文章写于 2021 年 1 月(yuè),如果接下来(lái)社区继(jì)续发(fā)展,Rust 将得到持续的改进,会(huì)变得更好(hǎo)并更易于 Web 应用(yòng)程(chéng)序开发。

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

 延伸阅读

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


">

    九游官方网页版-九游(中国)

    九游官方网页版-九游(中国)