Web开发人员的Rust-快速入门和快速飞行

大家好! 今天,我想分享我的学习语言的知识,以及如何以一种新颖,美观,优雅且非常有效的Rust语言使用非常流行的非阻塞异步网络连接来快速实现高负载网络项目。
我将在这篇文章中特别强调对语言和平台功能的快速而清晰的解释,因为我本人是具有丰富Web开发经验的专家。 有一个误解,认为Rust中的入口曲线非常非常陡峭。 但是,我将证明事实并非如此。 倒咖啡然后开车!

编程价值简史


为了使这些资料深入人心,很高兴简短地回顾一下过去50年来人们在编程中想要做的事情以及最终得到的结果。 没有冒犯,只有个人主观意见和喜剧,并有20年的开发经验。

低级语言:C,C ++


显然,您可以立即在计算机代码上以数字的形式编写程序,许多代码是在ZX Spectrum,BK0010-01和PC上完成的-代码原来非常快:-)但是我们是人,很多信息都不适合我们使用,我们分心了,因此即使是汇编程序的发明也无济于事-如此低级别的代码很少被编写且非常准确,而且很可能如果您不开发驱动程序,微控制器或棘手的嵌入式系统,那么这将对生活毫无用处。


在70年代初期,贝尔实验室发明了C语言,这是由于简洁的语法和非常“便宜”的抽象而扎根的,几乎成为了“便携式汇编器”。 很明显,如果您接受拉筋术,在C夜写作10年,不吃肉,祈祷并且不被社交网络和公平的性别分心,那么您可以编写非常有用且快速的程序,正如GNU雄辩地证明的那样,出色的生产性游戏,深受喜爱,但在Windows质量方面却是不可替代的,并且可以给出更多示例。
但是硬币的反面总是让人感到不舒服-由C语言本身的概念漏洞引起的,经常打开安全漏洞(创建了“软件漏洞”的整个行业)-编译器就像是不负责任的公牛,具有不可思议的力量,强烈的性高潮和短暂的记忆。 任何疏忽-您不仅可以删除程序(取消引用空指针,将指针释放两次,移出数组),而且不可撤销地破坏数据,并且很长时间都不会注意到它,直到客户端开始调用并且为时已晚(“未定义行为”,不同从编译器到编译器)。

BjörnStraustrup只是在80年代初才使情况更加混乱,为C添加了OOP功能。尽管C ++广受欢迎,但通常被视为一系列编程实验,具有不同的成功结果,包括 致命的 有时甚至从一开始就似乎没有C ++的感觉,或者它逐渐消失了,从而产生了一堆客观上复杂且相互矛盾的概念,每个新标准都在变得越来越多。 尽管“零成本抽象”的出色目标是使您能够获得快速的代码,以创建可靠的解决方案(如C),但仍需要满足以下条件:
  • 有“非常”一词的经验丰富的团队(多年的实践,编程的“修士”)
  • 好的静态代码分析器
  • 测试部门(在代码中,不负责任的编译器可能会留下漏洞,这会使他们长时间感到不舒服)
  • 所有团队成员都同意并严格监控的要求(未使用原始指针,严格封装,对变量命名,初始化对象等最严格的规范)


显然,要满足这些要求,特别是在企业对“无意外工作的代码”的需求日益增加的情况下,这是非常昂贵的。 这样的项目中的代码需要花费很长时间编写,需要经过长时间和仔细的测试,但是,有时,在没有C / C ++的情况下,Rust发明之前确实很难做到。

再次提供C / C ++的摘要-我们有一个功能强大但又不负责任的编译器,带有“当前抽象”,对开发人员的帮助很小。 结果,所有问题都传递给了程序员。 如果团队中至少没有一名程序员是经验丰富的人,那么就不会非常小心,并且不了解编译器的所有细节(实际上,没人知道所有细节,并且用户以后会找到它们)-等待麻烦。 但是,程序很快就可以正常工作,并且可能正确地进行了工作:-)当然,这催生了整个“拐杖”市场-静态分析仪,事实证明,应该由客户付费。 问题出现了—难道不可能编写出更严格和安全的编译器来帮助开发人员并诞生程序而不会带来意外和低级的安全漏洞吗?

Java,C#,Kotlin


坦率地说,对“不确定行为”的控制较弱,并且对C / C ++开发人员的要求很高,因此产生了创建安全开发环境的愿望,其中包括 互联网,大多数人都可以使用。 因此在90年代后期,Java出现了。
原则上,现在任何接受过不同程度培训的人都可以编写任何东西,并且它可以工作,并且无需加入程序-安全性和自发崩溃没有低级“漏洞”(几乎是,但它们已经是由错误引起的)在虚拟机中并集中固定)。 只有逻辑上的“漏洞”或隐藏的慢代码(由于算法的无知,算法成本的概念以及数据量增加时的放慢而诞生),这种情况并不那么令人恐惧,可以让您快速制作程序,并在必要时用合格的C / C团队重写小块代码C ++。
Java世界带来的有趣和有价值的事情如下:
  • 该代码是针对可在任何架构(Windows,Linux,Mac)上运行的虚拟机编写的
  • 开发人员不再像C / C ++中那样直接管理内存-这将传递给“垃圾收集器”的肩膀; 这消除了不确定的行为,谨慎的数据损坏和潜在的低级安全漏洞的风险
  • 该代码是在程序运行时动态编译的(Just_In_Time编译),因为 众所周知,程序中只有一小部分处于活动状态并且经常执行,因此编译所有内容都没有意义
  • 编写高效的多线程代码变得更加容易,因为 内存模型是严格指定的(比C ++早得多),但更多的是,并不是所有“几个线程的逻辑可靠性”问题都得到解决(例如,可能出现死锁或数据争用)


当然,这种友好平台的问世使编写许多有用和无用的程序成为可能,企业立即利用了这些程序。 对Java向后兼容性的支持长达15年或更长时间,使得该技术在企业界如此流行。

是的,我也比较不含酒精的啤酒和橡胶女士。

但是,Java并没有立即解决以下问题:
  • 由于从C / C ++继承的打字系统中的逻辑“空洞”,Java中的“空”类型进入了Java,迄今为止,这引起了许多问题,并不断导致“经验不足”的程序员崩溃不了解Optional类型,对函数式编程的monad不感兴趣
  • Java项目有时工作得稍慢一些,所需的RAM比垃圾收集器所需的C / C ++多几个数量级(我们有几个项目消耗数十或数百GB的RAM); 这通常不是直接的问题,因为 RAM价格正在下降,但“沉淀仍然存在”
  • 尽管进行了jit编译并进行了大量投资以加快垃圾收集器的运行速度(许多新事物经常出现并不断说:“尝试使用新的GC,它要好得多”),但20年来,一些高负载的Java项目一直在进行定期停顿几秒钟,不能以任何方式删除(存在强制垃圾收集,并且就像GC中的螺钉不会扭曲一样,垃圾收集器根本没有时间清理所有内容,甚至不能并行运行,甚至消耗比程序本身更多的资源)
  • 众所周知,由于相同的垃圾回收,Java GUI应用程序有时会冻结并减慢速度,从而引起用户的愤怒,这是没有希望的,也是无法预期的
  • 该语言的语法非常冗长和多余(您需要先写然后读“ public static functon”一词,而不要说“ pub fn”或“ def”),有时会迫使手指在指甲上折断并造成客观的疼痛和眼睛出血
  • 对开发人员资格水平的要求急剧下降,并且进入曲线足够低,导致产生了许多“奇怪的新鲜度”代码,这使我一生都感到难忘,令人难忘


阅读后,应该清楚所有内容都无法用Java编写,编写起来会很慢,然后会定期减慢速度并在负载下挂起一秒钟,有时会“吃掉”大量RAM(通常至少消耗了服务器上一半的RAM),生产性游戏您也将无法做到这一点,但是某些对性能和RAM并没有特别要求的业务逻辑(尤其是多线程)非常有可能且有用,因此,我们看到了这种技术在企业界的普及。 我们自己定期并积极地将Java用于公司的服务。

C#,Scala,Kotlin


“什么时候与Rust在一起?”-等待一分钟,您需要多做一些准备才能吃到这个甜柿子。 如果您不谈论其他技术的功能,那么您将无法理解Rust出现的原因以及为何如此流行和需求

因此,为使Java更好和更进步,Microsoft(以TurboPascal / Delphi的作者为代表)在零开始时提出了C#和.NET的概念。 客观地,据许多知名专家称,在开发人员的吸烟室中,“ C#”比Java更具性感,尽管到目前为止,尽管有Mono,但它仍然与Microsoft紧密相连,带来了所有的流入和后果:-)

Scala无疑是向前迈出的一大步,因为 当然,这种语言在科学上讲得很精深,从函数式编程领域吸取了许多有用和无用的东西。 但是在某种程度上,普及程度还不是很清楚。 但是, Apache Spark确实很好并且很受欢迎,无话可说。

Kotlin之所以受欢迎是因为 使Java对初学者来说更加高效和容易,尤其是在没有时间认真学习编程的移动平台上:-)

但是C#(在.NET平台上)以及Scala,Kotlin和其他JVM语言中的主要问题仍然存在。 垃圾收集器Carl在服务器上创建了明显的负载,并在负载和繁琐的RAM要求下停止了几秒钟的代码执行! 而且将不会出现多少种带有垃圾收集器和jit编译的语言,这些问题仍然存在,即使在理论上也没有希望。

脚本编写


“但是PHP呢?”。 是的,现在让我们谈谈脚本。 看来,他为什么呢? 大约在80年代末,很明显,如果您需要使用代码快速解决问题(不一定是超快速的),并且它是安全的,没有不确定的行为和低级漏洞,则可以编写脚本。 脚本以前是用bash,perl,awk编写的,但事实证明,在python中,您可以编写大型的大型脚本,尤其是科学的脚本,而且可以使用很多年!

Lua在游戏开发和机器学习( Torch ),JavaScript的浏览器和服务器端的Web开发中找到了自己的优势(每个人都知道Node.js 的“ npm”基础结构中授权是从Node.js和Golang生锈了吗?)。 Python-用于机器学习和数据分析以及系统脚本。 和PHP-完美地应对Web开发的服务器任务。


脚本编写的优势显而易见:
  • 使用流行的算法和数据结构(列表,字典,队列),在几分钟内创建了安全,高效的代码,没有不确定的行为和低级的安全漏洞
  • 快速启动和缓和的进入曲线。 花几天时间找出并编写有用的python脚本,通过Lua中的脚本更改nginx的逻辑,或使用PHP进行在线商店集成
  • 如果您以严格的方式,严格遵守CodeStyle的方式编写脚本,则可以快速解决复杂的问题并创建大型软件产品:Python中大量的科学库,PHP中大量的业务和网站管理系统,LuaJit中的神经网络平台Torch,非常出色的游戏脚本著名和受欢迎的游戏等
  • 某些类型的多线程编程(其中I / O是瓶颈)在python中可以很有效地解决。 而且,将最新的python功能与​​诸如“异步/等待”之类的期货结合使用,甚至可以更轻松地解决处理大量网络套接字的问题
  • 非常优雅地,即使没有多线程,异步处理大量套接字的任务也已在Node.js中解决了
  • 创建网页,使用大量的Unix库,使用PHP和基于它的产品仍然很方便
  • 在python,PHP,JavaScript上,数小时而不是数月内即可检验假设并制作原型非常方便
  • 对于分析和数据处理,传统上将python / R与大量高质量的库(熊猫,scikit-learn,seaborn,matplotlib等)结合使用非常方便。


但是,您不知道脚本编写的潜在缺点:
  • 在缺乏对开发人员的适当控制的情况下,有时入门级较低且脚本编写速度非常快,因此在任何平台上都会产生大量效率低下且难以维护的代码
  • 如果没有编译器,类型系统和静态类型,则需要对传递给函数和方法的参数进行动态检查,以确保类型和具有自动测试的高质量代码覆盖率。 否则,代码中的任何更改都可能会使程序和客户端(而不是开发人员)崩溃,这是第一个了解它的人(是的,我知道TypeScript,但这是拐杖和死灰泥)
  • 客观而明显地,在没有静态类型化和编译的语言中,运行时发生了很多事情,您不能使用大量的优化,因此,在某些任务中,脚本的工作量级变慢,并且消耗的资源也多倍。 尽管尝试在python(pypy),PHP(hhvm),JavaScript(翻译成机器代码,哇,Node.js v8),LuaJIT中实现jit编译,但我们不要自欺欺人:这看起来像是效率低下的拐杖。 原因是,并且需要一劳永逸地理解,由于语言的故意弱类型,脚本不太可能设法编写有效的运行时,从而加快了使用强类型Java / C#的速度。
  • 而且在python中,由于GIL的存在,似乎永远不会有像Java / C#中那样的高效多线程。


但是,由于 在大多数商业应用中,包括 站点管理系统,CRM等 由于大多数代码执行时间都花在查询数据库上,因此jit Java / C#的优势在PHP / python / JavaScript中编写解决方案的速度大大提高了,个人而言,在创建Web应用程序时,我会选择10-20行而不是10,000行Java / Spring中的字符串和一堆内脏。 并且PHP7以某种方式超频,因此其运行速度比python3 ;-)

在这里可以得出什么结论? 不必像现在流行的那样仅通过脚本来解决所有问题。在某些情况下,如果有充分的理由,则可以使用另一个更合适的工具:
  • 极高的网络负载和“极端”多线程,成千上万的网络套接字,等等。
  • 限制使用RAM和硬件
  • 密集的数据处理和计算,具有数百万个实体的矩阵,GPU(尽管python / numpy / pandas可以在这里提供帮助)


我们通常在公司中采用这种方法:
  • 快速在PHP / python / JavaScript / Node.js中做出决定,展开战斗并开始解决客户问题
  • 我们提供功能,机会,改善服务
  • 在极少数情况下,根据经验(通常不早于几年),一些功能已经稳定的服务将用C / C ++ / Java / Golang / Rust重写

因此,通常最好从脚本开始,开始一场战斗,然后,如果您直接需要它,则需要使用其他工具,通常这很顺利且没有风险。

函数式编程,Lisp,Haskell,F#


在开发人员的整个职业生涯中,许多人从来没有来过这里,而是徒劳的。 了解为什么FP(函数式编程)出现以及为什么在某些领域如此流行非常有用。

我将简单说明。 数学中有一个无法解决的问题,如“停止问题” 。 如果您用非常简单的语言来表达它,但是很严格,但是很明显,那么您将无法提出一种算法来证明该程序可以在没有错误的情况下运行。 怎么了 因为最初,人们开始使用以下命令进行主动性和强制性编程:
  • 可变变量
  • 有条件的循环
  • 副作用

他们开始犯错。 我们现在看到了这一点,在网络上以及在桌面和移动应用程序中都观察到大量的错误。 而且,无论您如何用自动测试覆盖代码,错误仍然继续泄漏,散布在地板上并咯咯地笑。
为了阻止这种越野车和无情的软件的恶梦,在50年代后期,出现了函数式编程和Lisp语言的方向。 现在,这组语言或多或少地代表了Haskell

尽管事实上,由于以下原因,代码中确实消除了许多可能的错误:
  • 代数数据类型及其上的模式匹配
  • 在伸出的手臂无法触及的范围内没有循环(仅通过递归)和可变变量(当然, 可以找到如何解决这个问题的拐杖)
  • 非常严格的静态类型输入,同时, Hindley-Milner类型的自动输出非常方便
  • 非常强大的功能,分为“纯”和“有副作用”,可以部分使用
  • 安全的并发编程支持
  • 在所有情况下都通过monad支持组合器(有关Rust的更多信息)


Haskell «» — , , , : . Haskell «», , , , . Haskell .

, , — Haskell , , , Java/C#.

, . Haskell , , , , . , «» — Haskell .

«C/C++» — Golang, D


, , C/C++ (, Forth). C++ , , D ( — , ).

, - /C++ c RAII , . 2009 Google Golang .
, Golang , , , . Golang Java/C# ( ) , … , Golang:
  • «green threads» ( ), python
  • , , (, Docker ) :-)

. Node.js python .

, Golang , Google vs Sun/Oracle, , , , :-) , « Java/C#» , — . Docker Golang . Golang — Java/C#, , .


, , Swift , c « » . macOS.

— Rust!


, , 40-50 , , , «zero-cost» , , - - :-) , , (Java/C#), , (C/C++), -, — . , ?

— . , , , 50 , 2010 Mozilla Research. , :
  • «zero-cost» ( C/C++ ), .. , , ------ « » (, , )
  • cargo ,
  • (* — ): , — « », , , ,
  • , , ( , , GC , - runtime, )
  • -, Null (!),
  • Haskell pattern-matching , Enumeration (Golang, , )
  • ( Java, )
  • ( Golang — ; Java/C#/Erlang .. , )
  • , ( C/C++)


, , -. ? ! , 50 . , , «» , , .

Rust «» , , Haskell .
Rust , , , Java :-) — Nulls, pattern-matching , generics traits . . , , , « Haskell», .

.

?


! , «» («ownership») . « » ( «lifetime», Rust book, ), ( , ).
/, . /, .
C++, - move-. , rust , .

zero-cost ?


, traits, . , , Golang. , , - pattern-matching, ( Golang, ). / . : . («borrowing»). , ( «read-write locks», ). , , , , .
slice , , , . — « » , , ; , .


:
  • ,
  • ( , )
  • , ---,
  • , , ,


, ( , ) … . / , C/C++ . .


, - , - . - , . Sync/Send, , : , , , .

, , 1-2 futures . , Node.js python async/await, , . , ?

unit


, unit . .

— cargo


Java, «Maven cargo » , : , . , , , .

Heap


, , heap. , , ( ) « » ( «lifetime»). , ( Swift). , — , heap . — , .


, , , . , (2 ) , , , , , AmazonWebServices c tls/ssl, .
, , , , , - . « » ( «lifetimes») .
, . IntelliJ rust , . Notepad++ — .

Rust


  • 了解您确实需要编写非常快速的系统代码,在高负载下工作并消耗最少的资源,并且没有诸如垃圾收集器之类的任何开销。 如果不是,请在python / PHP / Node.js中编写脚本,并在2-3年后返回任务(在97%的情况下,您不必返回)。
  • 不要害怕Rust。 该语言的主要亮点是一个非常聪明且数学上严格的编译器,不会遗漏任何危险和错误的代码。 通过反复试验,避免出现尖角,您将学习如何严格安全地使用编译器进行编程:-)
  • 从“ Rust book ”开始学习语言。 一本美丽,性感的书,易于阅读且阅读有趣。 但是最好阅读2次,“解析”所有示例。
  • 锈食谱 ”有很大帮助。 还需要“解决”它,看看代码是如何工作的,去感受它。
  • 有一些关于Rust的文献供高级用户使用,例如Rustonimicon ,但是我不建议您不必要为真正复杂的技术问题打扰



再次理解-为了掌握该技术,您需要开始在该技术上编写对公司有用的代码。 Rust编译器非常聪明,它可以提供保证(实际上,这非常重要,C ++编译器不知道如何做到)并且不会编译危险/内存损坏的代码,因此请进行尽可能多的实验,您将获得快速,安全的代码,并且甚至可以编写更好的程序:- )

项目实施细节


我将透露该项目的一些细节。 Amazon SQS每秒溢出数百个数据包。 队列由工作人员在本地读取,解析,每个消息由代理处理并重定向到另一个外部服务器。 有几个外部服务器。 最初,该解决方案是通过脚本实现的:脚本,占用操作系统中的进程,读取消息,通过网络处理和发送。 在几个功能强大的铁服务器(8个内核,16 GB的RAM)上,启动了数百个脚本(每个脚本!),同时从SQS读取,处理和发送数据。 简单,可靠,但是铁的摄入开始受到困扰。 铁的成本在不断增加。

Rust主要使用货物的标准库和模块:
  • rusoto_sqs-用于使用Amazon Web Services,没有问题,可以按需使用
  • rustls-用于tls,包括 与Google,Apple的TLS服务的交互以及客户端证书的使用
  • 信号钩-拦截杀死并结束交易以发送数据
  • 期货,tokio-rustls-与网络TLS套接字“ alazy”,多线程,异步工作,例如Node.js
  • 拍手-解析命令行参数
  • serde_json-json格式,这里没什么有趣的


不幸的是,它并非没有使用“不安全”的块和“ std :: mem :: transmute”-标准库找不到将二进制数据解析为树的工具。

最主要的,如果可以这样称呼,“插入”是在编译时发生的-由于binutils中的“过时的汇编器”,所以没有在CentOS6上编译库,但是在CentOS7上没有问题。

总体印象是,就资源和测试而言,Rust上的开发类似于“严格脚本”而不是系统编程,不长于脚本或Web开发。 在这种情况下,将执行严格的静态编译,不存在垃圾收集器和代数数据类型。

总体感觉是非常积极的。 尽管如此,仍然没有使用几个铁服务器(8个内核,16 GB RAM),而是通过一个进程(具有数十个线程)解决了问题,该进程占用的RAM不超过5 GB,并在内核上产生了不太明显的负载,流量约为0.5-1吉比特。

结论


好了,这结束了关于有效技术的漫长但希望有启发性和有用的文章。 现在,您知道了另一个工具,可以在需要时更安全地使用它。 我们回顾了编程语言发展的历史,它们的功能和特性,并且可能已经做出或将得出正确的结论。 祝您的项目好运,心情愉快!

PS:
*-是的,我差点忘了。 当然,您需要谈论不安全块。 在此块中,您可以:
  • 编写普通rust中不可用的内容-取消引用“原始”指针,并做一些更潜在的危险操作,有时是系统编程所必需的。 但是一切并不那么可怕:
  • 不安全的块是可见的,或者根本不存在,或者负责的代码集中在其中
  • 防锈标准库已经过全面测试,这意味着我们认为不安全的块在其中起作用
  • 流行的货物库也经过了全面测试(一切都内置在生锈的单元和集成测试中),因此可以安全地使用它们。

即 在“不安全”区中,您不能进行C中可用的任意放荡-只能进行某些特定类型的危险活动。 因此,您可以而且应该安静入睡:-)

Source: https://habr.com/ru/post/zh-CN477342/


All Articles