作者 | Andrew Israel
编译 | 王瑞平
Rust作为长期以来被看好的网络开发语言,更注重技术的稳定性,不掉链子,能够将设备的性能发挥到极致,更讲究精致。
相对于其它类型的语言来讲,Rust是新成员。最早由Mozilla于2014年4月9日发布,是一款高级通用语言,能够兼顾开发与执行效率。
虽然Rust并不是一个专属的网络应用开发语言,但是非常适合网络开发。编译器能就安全稳定方面的问题作出提醒。这使其具备了后端网络开发的独特优势。
我曾在《用Rust创建一家初创公司》一书中提到:“初创公司应优先考虑开发人员所带来的生产力而并非其能力。”对于一家创业公司创始人来讲,这种观点是明智的。也正因为如此,我喜欢使用Rust,并雇佣了同样喜欢使用Rust的开发人员。
我必须提醒:如果你的团队中没有其他人会使用Rust,那么,教授所有同事使用Rust的成本将会很高。他们可能需要一段时间才能游刃有余地使用Rust,在此期间,你需要指导他们,工作效率会因此下降。明智的选择是使用团队其他人都知道的语言,除非你真正需要使用Rust。
幸运的是,我的队友已经了解并喜欢上了使用Rust,并熟知如何让代码生成工具(如,Serde和Diesel)最大效能地发挥作用,以成为更好的Rust程序员。
我的团队为Cloudflare建立了数据泄露防护系统。该系统通过对网络流量进行“扫描”确保私人数据没有被泄露。例如,它可以检测并阻止黑客从你的数据库中上传数百万个信用卡号码到pastebin.org,或者阻止某人将带有特定office标签的word文档发送到你的yahoo.com电子邮件。
实际上,我们可以将扫描网站以防止数据丢失的服务想象为数据泄露防护系统扫描仪。在此过程中,系统可能同时代理很多http请求,对性能敏感。
我们不希望用户在打开数据泄露防护系统时,网页浏览速度变慢,并因此提供了两种构建后端API可供选择的语言:Rust和Go。
无论使用哪种语言,构建出的后端API必须能够与数据泄露防护系统进行互操作,并能够共享一系列类型,如:表达用户配置等。API服务器将用户配置序列化为JSON,数据泄露防护系统将在需要扫描请求时反序列化该JSON。
在实际操作过程中,我更倾向于用Rust语言编写所有序列化和反序列化程序。此外,我个人比较倾向于在系统的不同部分之间共享代码,针对性能关键型服务和非性能敏感型服务使用Rust可以大大简化整个代码库。
虽然Rust在构建数据库方面并不出色,但我还是认为它在此方面性能优良。
就拿Rust语言中的Diesel框架来说,它能够从SQL数据库语言之中迁移生成类型化SQL模式,从而生成所有SQL查询。此外,当更新SQL模式时,Diesel将重新生成适当的Rust模型。
实际上,在Rust类型系统中构建SQL模型会导致一系列问题,包括:错误消息超过60行、毫无意义的错误信息、很难将公共代码分解为共享函数等。
但总的来说,如果你的应用程序在很大程度上依赖于数据库的许多功能,我认为有必要确保你的数据库查询获得了正确的检查类型。
数据库查询不是API后端中可选的额外内容,它们几乎是你的整个代码库。所以确保它们正确是值得的。
运用Rust语言中的Diesel和Serde框架,你可以在API中生成几乎所有重要的代码(读取请求、执行数据库查询和编写响应),从而使你有更多的时间来编写业务程序、发布特性并进行业务建模。
重要的是,存储用户配置的后端API能够在软件中正确地模拟现实世界。如果用户想在软件中模拟办公室布局,类型系统就能够直接对办公室建模,而不必让用户推送无效配置。
用户往往希望在编译时而不是在运行时检测到无效的配置,从而尽量减少测试和错误代码。例如,用户的办公室不可能同时位于两个时区。那么,你的软件模型就不应该能够表示具有两个时区的办公室。
对了,Rust有两个特性可以帮助你准确地进行业务建模:枚举和不可克隆类型。
重点说下Rust的枚举特性。它还可以被称为“和类型”、“标记联合”、“代数数据类型”或“带有关联值的枚举”。这取决于你使用的语言。我个人比较喜欢求和类型,iphone开发者可以在Swift中使用。
准确地进行业务建模是我在构建高级API中非常关心的事情,正确性至关重要。如果需要确保我的软件模型准确表现出现实世界,Rust比Go更好用。
现在谈谈Rust的“不可克隆类型”特性。在实际操作过程中,如果其中一个IP是“不健康”的,并断开了Cloudflare连接,那么,Cloudflare需要避免重复使用这些IP,并使用一些以前没有用过的IP。
应确保每个IP都有三种状态:正在使用、未使用和以前使用过但现在“不健康”。这些IP中的每一个都可以分配给四个长时间TCP连接中的一个。
这听起来像是一个很容易解决的问题,但在实际操作过程中很难对“每个IP地址最多只能分配给一个连接”的想法进行建模。我必须编写大量单元测试程序,以找到两个不同的连接获取相同IP地址的边缘情况。
Rust可以很容易地确保特定值只在一个地方使用。这需要确保使用该值的函数都必须引用它,或者确保你的类型没有被强制Clone,并保证使用它的函数拥有该值的全部所有权。这样,该值将能够在移动时移动到函数中,函数可以在完成时返回该值。
所以,如果我想在Rust中实现上述操作系统,只需要保留我的10个IP地址的HashSet,也要确保IP没有派生克隆出新类型。因此,Rust的“不可克隆类型”特性至关重要。
对于你的初创公司来说,系统的可靠性很重要。我们提供的Rust后端服务的优点是它从不崩溃。在实践中,Rust通常有更好的方法处理不同的选项。
而这种可靠性肯定会带来开发人员的额外开销,比如,考虑如何正确地匹配所有的Result和Option值。但对于许多领域来说,这种付出是有意义的。
值得注意的是,Rust不倾向于使用太多内存(如TCP连接或文件描述符)。因为当函数终止时,所有内容都会被删除和清理。
在实际应用过程中,性能问题最终会变成可靠性问题。如果你的服务泄漏内存的时间足够长,或者摄入了足够多的数据,那么性能存在的瓶颈可能会导致服务器宕机。
Rust作为高级系统编程语言做出了令人满意的成绩。当你在网络开发时,它可以通过Serde和Diesel节省开发者时间。神奇的是,虽然类型系统简化了业务建模过程,但是服务质量却不会因此下降。
对于Rust语言的使用效果评价并不是绝对的,需要根据不用的情况进行判断。如果你的团队没有过多的Rust使用经验,在网络开发时使用Rust可能会带来非常糟糕的结果。Rust的使用难度极高,你应该根据具体情况引导团队使用熟知的语言。
公司会根据不同的情况使用不同的语言。在Cloudflare, 我们执行大多数对性能敏感的服务过程中使用Rust,执行对性能宽松的服务(如,API后端)过程中则使用Go。我公司的团队过去使用Go语言进行后端开发,由于上文提到的原因逐渐迁移至Rust语言。
对于使用Rust语言的不同权衡并非对每个团队都有意义。这主要是由于学习Rust和在Rust中重写核心业务库需要耗费巨大成本。即便如此,仍有越来越多团队愿意考虑使用Rust作为其在后端开发过程中使用的语言。
总之,不同公司应该根据自身的情况使用熟知的语言完成工作。如果你的团队已经熟知Rust,那么,在完成高级项目过程中使用它绝对是明智的。
参考链接:https://blog.adamchalmers.com/why-rust-on-backend/