只接受发布货源信息,不可发布违法信息,一旦发现永久封号,欢迎向我们举报!
1064879863
16货源网 > 餐饮行业新闻资讯 > 软件开发 >  为何 Elixir/Erlang 相对冷门?


为何 Elixir/Erlang 相对冷门?

发布时间:2019-09-02 23:18:29  来源:网友自行发布(如侵权请联系本站立刻删除)  浏览:   【】【】【
updated:好像回答这个问题的都是特定人群,我匿了,没意思。补充几点:elixir 是强类型,这点我用词不当,但的确也是动态类型的语言,debug 起来和 js py 一类真的有差吗?评论区那种变
为何 Elixir/Erlang 相对冷门?

updated:

好像回答这个问题的都是特定人群,我匿了,没意思。

补充几点:

elixir 是强类型,这点我用词不当,但的确也是动态类型的语言,debug 起来和 js py 一类真的有差吗?

评论区那种变量不可变非得不断赋值的操作我没见过,我也挺喜欢变量不可变的特性,如果不是这些特点估计也没人用elixir了

我说的都是 elixir 让人头疼的地方,不接受可以无视,切题而已。要不然谁无聊会在这上面花时间学什么 er 一类的语言。


原回答:

我们用的 Elixir 开发,如今一年多过去了,总结就是:

非强类型语言 debug 真的很头疼,写的代码不 run 一次根本不知道会怎样。而且所谓的开发效率都会在项目变大后失去语言间的优势,这么大的项目 Java 一样优秀。

对于分布式来讲使用otp简直不要太爽

(其实国内开发游戏服务器使用erlang蛮多的

工作岗位少是因为难招人(我已经体验过了

和现在库还很少,elixir就Phoenix能拿得出手

Erlang & Elixir 工作岗位很少,也很冷门。

因为很多人被它的高并发吸引,但是作为一个职业的玩家,负责人的告诉你,不能全信。

Erlang最大的优势就是稳定和计算可预知性。

面向OO和面向FRP编程,永远都是两个比较极端的场景。

有几个人真理解React背后的数学思想了,又有几个人真正用好了它的模式了。这句话放到Erlang和Elixir上也是同样的道理。

利益相关:Fullstack, CRUD BOY

因为其它语言照样能提供高并发以及容错的特性,Erlang的思想已经在业界被吸收了,Actor model已经在其它平台有框架应用了,而其它语言有大量从业者,因此Erlang/Elixir火不起来。

有些事情是说不通的,比如:

“OTP超好的。。。。“

然后,业界终于用比较暴力的方式 “Docker/K8s” 和比较晦涩的新概念 “CI/CD”,实现了Erlang/OTP早就实现了的功能。

所以说,为啥Elixir/Erlang会火,就算给一万个理由业界也普遍选择折腾docker集群。

Erlang 算不上冷门,至少你还知道名字,很多你连名字都没听过的才算冷门。(但是很多冷门的设计理念却非常先进)

Erlang 在高并发方面有优势这个说法,其实非常片面。Erlang 最牛逼的地方是它是目前唯一一个具备软实时(Software Realtime)级别的系统。Java 模精品不了,Go 模精品不了。当然如果你要用 C/Rust 之类来做是可以的,但是其实就是把 Erlang 再做一遍而已。

这个软实时指的是垃圾回收性能平稳。如果做语音类应用,需要网络传输过程不会因为 GC 回收导致延迟抖动,Erlang 是你的开箱即用的最佳选择,没有之一。

“听起来也没多牛逼。不就是 GC 技术的优化嘛。我搞个并发式 GC 算法不就行了?“——说这话的,只能说第一并不了解 GC,第二也根本不知道 Erlang 的恐怖之处。只能说朋友,你对力量一无所知。这里不想展开八百字复读机式介绍。自己可以看看 Erlang VM 的设计介绍。你会明白为什么 Erlang 里的 GC 才是真正完全并行,绝无 Stop the World 可能,而且回收延迟柔性可预测的。这一切不是没有代价的,代价就是变量必须绝不能被共享,而且不能被修改。这一来 Java 之类的 C 家族语言还玩个啥,凉了。

另外一些回答里,看了一圈,其实很多也只是随便用了一下试试。说几个点:

1、Erlang 是官方自带一套静态类型分析系统的——dialyzer,你不需要完全标注所有类型,未标注的可以自动推导;官方建议你在所有项目里都默认使用它来检查项目,如果你遵循这个建议,那么你还能享受自动生成文档的好处;而且官方标准库里也都写了类型。

为什么 Erlang 没有把静态类型分析作为吹的点?

因为静态类型系统(编译期检查)其实有其局限性,特别是分布式系统下,两个系统 A 和 B,假设某数据类型做了升级,那么实际系统升级里,会出现 A 升级了,B 还处于旧版本的情况。这个时候还有个屁的类型一致。所以依赖于静态类型分析保重系统一致,只能对于单个非分布式系统比较好。对于真实的分布式系统,设计出发点根本不是类型一致。而是即使不一致,也要能容忍。这就是另外一个话题了。

额外提一句,Erlang 的类型系统是在不允许你自己定义新类型的基础上,却能够完美的满足你的类型要求的设计。说真的,没有人和我提过这一点,但是当有一天我突然意识到的时候,那一瞬间是极其震惊的……(想想 Haskell)

2、Erlang 自带源代码变换系统,这玩意儿用人话说就是,你可以对你自己的源代码进行变换。比如 Erlang 官方自己的 EUnit 库,它是一个单元测试库。它的原理是什么?实际上就是当你引用 EUnit 的时候,就会导致你的当前模块增加一个 parse_transform 标记。然后编译期就知道这个模块需要被外部重写。最终实际上是交给 eunit_autoexport 模块来处理。

这个机制不是特权。你自己也可以用。但是这个 feature 确实比较高级,比较少有人讨论。

前端工程师熟悉的 Babel 其实做的就是这件事。只不过差别在于,Erlang 直接把这个做到了内部而已。而且非常简洁。大部分时候都用不到这个。当时当你有那么一两个 feature 真的需要用牛刀的时候,你一定会发出卧槽太爽了的评价。

3、Erlang 的模块系统是我见过最人性化的,简单到小学生都能明白。你不需要 import 任何模块。你想使用,就直接使用。Erlang 会为你自动寻找并加载。朋友们,其他语言头部那一堆 import 怎么说呢,真的是脱裤子放屁的存在。因为 Erlang 的语法保证了,能够简单的扫描当前文件就能推导出到底使用了哪些模块。

模块可以在不停止系统的情况下安全的热升级。是的,热升级其实 Python、JavaScript 之类的用点 Hack 小技巧,也能模精品个七八分。问题是没有一个敢说“安全”。因为 Erlang 的模块热升级是多版本并存的。假设一个进程真正跑,它使用的是老版本模块。那么升级的时候,新进程会使用新版本。互不干扰。

即使新版本带来了新问题,你还可以无缝的降回去。当然,你愿意,也可以把老的进程干掉一些,直接强制到新版本。其他系统这么做实在太可怕。可是 Erlang 的进程是容错的,状态可恢复而且可升级的,所以这么做还是可行。

模块热更,只是应对一些局部小修改。如果模块间有复杂依赖,需要一次进行多个模块热更怎么办?放心吧。Erlang 有完整的方案。

4、其他语言里,程序基本上就是,一个主入口,然后调用其他第三方模块这样的设计。但是这个设计太简陋。Erlang 的设计是,整个系统是由一系列独立运行的 Application 组成的。没错,其实你只是在为 Erlang 这个系统里开发 Application。包括俗话说的“系统标准库”这种玩意儿,Erlang 里也是独立的 Application。

有何区别?每个 Application 都有自己的一个启动过程,自己的一组进程(构成监督树,具备独立的容错性)。相互之间运行时耦合是松散的。所以,A 和 B 两个 Application 你想运行在同一台计算机,或者多台不同的计算机上,代码有差别吗?没有。

你感觉到一丝奇怪的气味没。是的,Erlang 甚至有自己的 Shell 用来管理和控制这整个系统。而这个 Shell 里就是 Erlang 语言本身。完美的一致,简直是操作系统一样。

顺带一提,Erlang 是可以写脚本的,叫做 escript。原汁原味,保证鲜美。

5、一般语言的字符串处理,感觉很方便。但是很多语言内部是只能处理 Unicode 的某一种编码的(UTF-8、UTF-16BE 是流行选择)。如果想要随心所欲的去支持,就必须把字符串当作原始二进制数据处理。但是 Erlang 里根本没有这个问题。

这个展开说比较复杂。很多人抱怨 Erlang 里字符串处理好像不方便。一个重要的原因是,这部分的理解需要稍微深一点的基础知识(不复杂)。以后再展开说

6、Erlang 里面直接包含了几种设计模式。而且只需要这几种设计模式。是的,比如 Erlang 里是自带状态机模式的。说到这里……

其实应该能 get 到为什么 Erlang 不流行了吧??

我可以向你介绍很多 Erlang 的好。而且我可以保证它真的就是这么好。但是即使你能理解,你能学会,其他人能吗?很多人根本不在乎到底一个技术好不好,他们只在乎到底几天能够学会,多少工作岗位,以后跳槽好跳吗。

这没有错。甚至很正确。因为大多数人还是以活着为前提在努力。市场是面向大众的。大众是平凡的。平凡的人,用平凡的产品。只有疯子,才会追求有趣和不凡。

这就是为什么,Whatsapp 能在创业后不久,就以 190 亿美元卖给了 Facebook;而他们只需要 50 个工程师,就能支撑 9 亿用户。

https://www.wired.com/2015/09/whatsapp-serves-900-million-users-50-engineers/

其实 Erlang 也并不是什么万金油,任何项目都适合。不是说选择了什么牛逼的东西,自己就会变得牛逼。而是因为一个工程师有牛逼的思想,才能够在茫茫工具海洋里,看出哪一个才是真牛逼。

使用工具也要付出智力代价的。真的不是免费的。

看了这个问题的几个回答和评论,感觉不用/不会 erlang 就是你智商不足/能力不行/眼光不够。这些 erlang 的簇拥根本没有领会毛泽东思想,它如何火的起来?

你为啥那么确定erlang的高并发模型其他语言就没有呢?

不就是那个actor model嘛

scala的对应产品就叫做akka

https://slideplayer.com/slide/6333171/

所以其实jvm上已经有了erlang式的高并发模型,作为先驱,有不少公司使用了这个东西

however,但是

其他语言也纷纷借鉴了这个模型,你看看wikipedia上那个actor model的条目,下面罗列了一大堆框架和工具,你自己数数有多少,岂止是一种,所以erlang的优势就被抵消掉了,加上其不太正宗的语法,用的人少也就不奇怪了

vert.x还改进了actor model,其原型是eventloop+handler的模式,并衍生出了新的概念verticle,也许你更应该了解一下墙外世界发生了什么,这里有一个新兴并发模型的介绍,需要自备梯子翻墙,actor只是其中一种而已了

New Concurrency Models on the JVM: Fibres, Verticles, Agents and Actors. by Lutz Huehnken

世界变得很快,要努力学习跟上时代,表刻舟求剑

以前投过一家二刺螈游戏公司,后端就是用的erlang

现在写程序大多都是不用脑硬怼CRUD编译过了能跑起来就谢天谢地了,内存泄露?微服务定期重启就完事了;死锁?重启就完事了;热升级?我都没事就重启了,不需要的;性能指标?谁知道,用户没投诉就行。

FP这么些烧脑的东西,erlang追求的东西,在这个时代的大部分公司成了屠龙技,有很好,没有似乎也不影响,只有在很特定的场合,才有龙可屠。

这也解释了为什么程序员这职业对年长的人不友好,你辛辛苦苦练成了屠龙战士,资本家要的只是流水线上切猪肉的工人,连熟练都不太需要。

这是最好的时代,也是最坏的时代。

Erlang 至少这个名字不冷门,即便没有用过很多也都听过,而 Elixir 才真的算典型的小众语言。实际上优秀的小众语言几乎都有某些方面的优势,那为什么它们还是小众呢?

所以,一门语言(某些方面)有优势完全不能决定它是否能火起来。假设这门语言全方面都是优势,碾压其它语言,把它们按在地上摩擦。典型的例如存活在宣传中的 V 语言,能火。

最近华为的鸿蒙走了类似 V 语言的路,在宣传中从各方面吊打其它系统,放大其它系统的已经不存在的缺陷,碾压一切、毁天灭地。这样宣传,能火。

早期 Erlang 所属公司爱立信对待 Erlang 是很封闭的,导致 Erlang 早就错过了宣传自己的黄金时期。

即便是现在 Erlang 也是如此,近来出现的 EEF(Erlang 生态系统基金会)还是 Elixir 的那批人带头联合搞起来的。他们(指 Elang 团队)对宣传太不上心了,例如连 Twitter 上都只有在 Release 新版本的时候才产生一个动态。那批元老还在用邮件列表。现在的新人接触以后想深入获得更多的信息,太难了。简单来说,官方的回馈信息太少。而这些方面 Elixir 比 Erlang 要强数倍,不过这确实也是新语言应该做好的。


再说一般人理解的高并发优势。实际上现在来看,在高并发上 Erlang 已经排不上位置了。Rust 中 Actor 模型的实现 Actix 框架和 Java 的 Vert.x 在并发上都能吊打 Erlang,甚至从数据方面看根本不在一个级别。参照:https://www.techempower.com/benchmarks/

但这不表是它们比 Erlang 强。因为 Erlang 真正的优势在于抢占式调度带来的低延时和软实时性。无论是 Actix 还是 Vert.x 都需要将耗时的计算任务分配给原生线程,此时它们的并发性能就会大打折扣,否则直接堵塞。但是这在 Erlang 上是不存在的问题,这是调度方式决定的。Go 也是高并发的语言,Go 很成功,已经是热门语言了。而 Go 的调度实现是不完全的抢占。

简单来说,高并发太多语言都能做了,现在来看把这当做是一件无可比拟的优势才很奇怪的事情(所以这个问题很奇怪)。Erlang 高并发的表现不是最突出的,但是确是最实际的。现实的情况是没有那么多理想环境让你纯粹的异步执行所有任务。

另外从这个问题就能看出来一般人对 Erlang 的理解太片面了,对 Erlang 的优势总结也太片面了。这个片面就好像很多使用 Erlang 的人企图用一句话总结 Erlang 的列表数据结构优缺点,实际上列表的坑远远比大多数人想象的多。我非常建议看 @林建入 的答案,他说了很多 Erlang 的优点。

Erlang 从语法和理念上和其它常见语言有很大的差异,所以有人说学习 Erlang 就是 Unlearn 其它语言。很多人可能不知道,改进了 Erlang 语法的 Elixir 实际上比 Erlang 更加罗嗦(哈哈哈)。


最后说下这个问题下的误解:

第一个是有关 Erlang 和 Docker/K8S。这是完全不同的两个东西,它们作用在完全不同的地方。OTP 是管理 Erlang 进程(最小执行单位的抽象,在概念上类似于 Go 的 Goroutine)的,和管理 ERTS(Erlang 运行时)进程的 Docker 或其它容器技术并不在一个层面上。使用 Elixir 是完全可以使用 Docker 的。如果可能,Go 也可以设计出管理 Goroutine 的监督树模型,这跟怎么部署应用八杆子打不着,使用 Docker 也可以让分布式进程之间使用原生协议通信。这种错误观念不只第一次见了。

第二个是有关 Elixir/Erlang 类型系统的。将 Elixir 当作弱类型的一般是没有分清「静态类型」和「强类型」。Elixir 当然是强类型的,但非静态类型。

另外实际上我是 Elixir 开发者,对 Erlang 不是很了解。知乎上也有非常了解 Erlang 历史的人,例如 @bhuztez(可惜被封了),推荐阅读一下他们以前的讨论。

哪里冷门了,页游后端不全是erlang吗,火热的很

我是一名少儿编程公司的程序员,平时用Go 语言比较多。

如果说Elixir是自由精英主义的话,那么go就是新生代的实用主义。

在外网看到一篇比较Elixir和go的文章,写的非常全面,翻译搬运过来分享给大家。篇幅较长,希望大家耐心阅读。

原文:https://blog.codeship.com/comparing-elixir-go/

比较不是说服你学习某种编程语言,排斥另外一种,而是通过这种比较,了解每种语言在设计上所作出的权衡取舍。帮助我们在面对特定问题的时候,做编程语言层面的抉择。

原文翻译:

  • 背景

Go 语言 是 2009 年由Google开发的,编译后是平台相关的本地可执行文件。 开始的时候,它是作为一个实验性的项目,目的是针对其他编程语言的主要槽点,并保留他们的强项,开发出一个新的编程语言。

Go 的目标设定是,平衡开发速度,并发,性能,稳定,可移植性 和 可维护性,而它也完美的达成了这一目标。所以呢,Docker 和 InfluxDB 就是用 Go 开发的,并且 很多主流公司 (包括 Google, Netflix, Uber, Dropbox, SendGrid 和 SoundCloud) 也在使用 Go 开发各式各样的工具。

Elixir 语言 是 2011 年由来自 Plataformatec 的 Jose Valim 开发的。它运行在 Erlang BEAM 虚拟机之上。

Erlang 最早是 1986 年,由爱立信公司为高可用,分布式的电话系统开发的。此后 Erlang 被拓展到了许多其他领域,比如 web server, 并达到过 9 个 9 的可用性 (31 毫秒/年 的宕机时间)。

Elixir 的设计目标是,让 Erlang VM 有更高的可扩展性,更高的生产力,同时保持跟 Erlang 生态圈的兼容性。为完成这一目标,它允许在 Elixir 代码中使用 Erlang 库,反之亦然 (Shawn: Erlang 代码中也可以使用 Elixir 库)。

避免重复,下文将Elixir / Erlang / BEAM称为“Elixir”。

许多公司在生产环境中使用 Elixir。包括 Discord, Bleacher Report, Teachers Pay Teachers, Puppet Labs, Seneca Systems, 和 FarmBot。很多其他的项目是用 Erlang 构建的,包括 WhatsApp, Facebook 的聊天服务, 亚马逊的 CloudFront CDN, Incapsula, Heroku’s 路由和日志层, CouchDB, Riak, RabbitMQ, 还有占世界将近一半的电话系统。

  • 编程风格

要对 Elixir 和 Go 作出一个可靠的比较,就要理解他们各自的 run-time的核心原则。因为这些基础构件是其他一切的来源。

对于有传统的 C 风格编程背景的人来说,Go 的风格看起来会更熟悉一点,即使这个语言做了一些更偏爱函数式编程风格的决定。你将看到你感觉很熟悉的 静态类型,指针,还有结构体。

函数可以从结构体创建,也可以附着于结构体上,但以一种更加可组合的方式。函数可以在任何地方创建,并附着到类型上,而不是把函数嵌入到对象里,然后再从那个对象扩展(继承)。

如果需要使用多个结构体类型调用一个方法,可以定义个接口,来提供更好的灵活性。跟通常的面向对象的语言里的接口不大一样,那些语言里,必须在一开始定义某个对象实现了某个接口。在 Go 语言里,接口可以自动的应用在一切匹配得上的类型上面。这里有一个 Go 接口的好例子。

Elixir 更偏爱函数式风格,但掺杂了一些来自面向对象编程的原则,让它看起来没那么另类,让来自面向对象领域的程序员转到函数式风格时更容易些。

Elixir 里变量是不可变的,并且使用消息传递(来共享数据),所以不会把指针传来传去。这意味着函数操作在函数式编程里面更加“字面化”:传进一些参数去,然后返回一个没有副作用的结果。这简化了开发中的很多方面,包括测试 和 代码可读性。

由于数据不可变,所以像 for 循环之类的常见的操作,在 Elixir 里面没法用,因为你没法儿递增一个计数器(counter)。这种的操作要用递归实现,尽管 Enum 库以一种很方便的形式,提供了一些常见的循环模板。

因为递归的使用是如此频繁,Elixir 也使用 尾递归: 如果函数的最后一个调用是调用它本身的话,调用栈不会增长,避免了栈溢出的错误。

Elixir 到处都在使用模式匹配,有点像 Go 使用 接口 的那种方法。在 Elixir 里,一个函数可以这样定义:

通过使用 map 模板作为参数,仅仅当传进来的参数满足以下条件时,这个函数才会被调用:参数是一个 map 类型,并且有个 "data" 字段,"data" 字段的值又是一个嵌套的 map 类型,它里面包含了一个值为 "bob" 的 "nifty" 字段,以及一个 "other_thing" 字段。

匹配成功的话,参数的 "other_thing" 字段的值会被赋值给 other 变量,在函数体内就可以访问other 变量了。

模式匹配被用在各种地方,从函数参数到变量赋值,特别是递归。这里有几个例子来帮助理解模式匹配。结构体可以被定义为类型,然后一样被用在模式匹配里。

这些方式(Go 的 Interface 和 Elixir 的模式匹配)在原则上很相似,都将数据结构和数据操作分离开来,都用匹配的方式来定义函数调用,Go 是通过接口,而 Elixir 通过模式匹配。

虽然 Go 允许通过特定的类型来调用一个函数,比方说 garea(), 但跟 area(g) 基本是一回事儿。这方面,两种语言唯一的区别是,在 Elixir 里,area() 必须返回一个结果,但 Go 里 area()可能潜在的操作了一个内存中的引用。
由于这种方式,两种语言都有很强的组合型,意思是说,在一个项目的生命周期中,不用去操作、扩展、插入庞大的继承树。这对于长时间运作的大项目来说,是个大福音。

这里最大的区别是,Go 语言里,模板被定义在函数之外,用来复用,但如果没有组织好的话,可能会导致很多重复的接口。而 Elixir 没有办法这么容易的复用模板,但这样模板就一直定义在它使用的地方,不会乱。

Elixir 是强类型的,但不是静态类型的。大多数时候,是通过类型隐式推断来判断类型的。Elixir 里面没有运算符重载,当你想用 + 来串联两个字符串的时候,一开始就会比较困惑,因为在 Elixir 里,应该用 <>。

如果你不理解背后的缘由的话,这种设定看起来好没劲。编译器能用明确指定的运算符来推断 +的两边都是数字,类似的,<> 两边都必须是字符串。

强类型基本上意味着,通过使用 Dialyzer,除了一些在模式匹配里有歧义的参数之外,编译器几乎可以捕获所有类型。在这些例外情况下,可以使用代码注释来定义变量类型。这样做的好处是,在不丢失动态类型的灵活性的和元编程的特权的同时,享受静态类型的大多数好处。

Elixir 文件可以用 .ex 扩展名表示需要编译的代码文件,.exs 扩展名表示边编译边执行的脚本文件(就像 shell 脚本等一样)。Go 是一直需要编译的,但是 Go 的编译器太快了,即使编译巨大的代码库,感觉也是瞬间完成。

  • 并发

并发是比较的重点,现在你已经对编程风格有了一个基本轮廓的了解,所以接下来将更容易理解一些。

传统上,并发是通过线程来实现的,但线程比较“重”。最近,各种编程语言开始使用“轻量的线程”或者叫“绿色的线程”,并使用存在于单线程里的调度器 轮流调度不同的逻辑。

这种并发模型让内存使用更加高效,但这依赖 runtime 来规定调度的流程。JavaScript 已经在浏览器里使用这种模式很多年了。一个简单的例子:当你听到 JavaScript 的 “非阻塞式 I/O” 时,就意味着,那个线程里执行的代码,会在 I/O 开始时,把控制权让渡给调度器,以让调度器做一些其他的事情。

  • 合作式 vs 抢占式调度

Elixir 和 Go 都使用调度器来实现各自的并发模型。虽然这两种语言天生都会将任务均分到多处理器上,但 JavaScript 不行。

Elixir 和 Go 是用不同的方法实现并发的。Go 使用的是合作式的调度,这意味着,当前正在运行的代码必须主动让出控制权,另一个操作才能获得被调度的机会。而 Elixir 使用的是抢占式调度,每个进程都会被预置一个执行窗口,这是被强制保证的,任何情况下都如此。

在性能方面的表现上,合作式调度更高效,因为抢占式调度在强制保证执行窗口时,产生了额外的执行损耗。抢占式调度更稳定(Shawn: 是说表现稳定,不是工作稳定。比如,在吞吐量上,抢占式调度表现更稳),意思是说,不会因为一个大而耗时的操作一直不释放控制权,而推迟数以百万级的小操作的调度。

作为预防措施,Go 程序员可以通过在代码里插入 runtime。Gosched() 来更频繁的(主动)归还CPU控制权, 来应对这种潜在的代码问题。(Elixir 的)运行时级别的保证让第三方库和软实时系统的可信度更高。

  • Goroutine vs 轻量进程

为了在 Go 里执行并发操作,我们用 Goroutine,就是简单的在一个方法前面加上一个 go 关键字,任何方法都行。所以这样:

这方面 Elixir 与之差不多,它不用 Goroutine,但你可以 spawn 一个 进程(澄清一下:不是操作系统的那种进程,以后所说的进程都是轻量进程)出来。

这里主要的不同是,go 操作什么都没有返回,而 spawn 返回了新创建的进程的 id。

两个体系的进程间通信风格相似,都使用“消息队列”来通信。Go 语言里称其为 “ channel”,Elixir 里叫它 “进程信箱”。

在 Go 语言里可以定义一个 channel,只要有这个 channel 的引用,任何进程都可以给 channel 发消息。在 Elixir 里,可以通过“进程ID”或者“进程名”来给进程发消息。Go 的 channel 在定义的时候给消息指定了数据类型,而 Elixir 的进程信箱用的是模式匹配。

给一个 Elixir 进程发消息,等同于给一个 Goroutine 监控下的 channel 发送消息。这里给出个例子:

go channel:

Elixir process mailbox

另外,两者都有办法设置接收消息的超时时间。

由于 Go 是允许内存共享的,Goroutine 也可以直接改变一个内存中的 (channel) 引用,虽然这时必须使用互斥锁来防止竞态条件。比较理想方式是, 一个 channel 上,应当只有一个 goroutine 监听并更新其共享的内存,以避免互斥锁的需求。

在这个(基础)功能之外,好戏才刚刚开始。

Erlang OTP 提供了一系列 使用并发和分布式的 “最佳做法” 的模板(叫设计模式也行)。在 Elixir 里,多数情况下你不会直接去碰 spawn, send/receive 这些基础函数,一般我们都用这些抽象提供给我们的功能。

这些封装包括 Task,用来做简单的 async/await 风格的调用。Agent 用来使用并发的进程来维护和更新共享的状态。GenServer 用来做更复杂的逻辑。等等。

为了限制一个队列上的并发数,Go 的 channel 实现了接收指定数量消息的 buffer (达到上限时,阻塞发送者)。默认情况下,在有进程准备好接收消息之前,channel 一直被阻塞,除非设置了 buffer。

Elixir 的信箱默认是不限制消息数量的,但可以使用 Task。async_stream 来定义一个操作的并发数,然后跟 Go channel 一样的阻塞发送者。

进程在 Elixir 和 Go 里都很轻量,一个 goroutine 差不多 2KB,而一个 elixir 进程差不多 0。5KB。Elixir 进程有自己的独立内存,在进程结束时自动回收,而 goroutine 使用共享内存,资源回收要使用应用程序级别的垃圾回收。

  • 错误处理

这可能是两者间的一个最大的区别。Go 语言在各个层级非常显示的做代码处理,从函数调用到 panic。 但在 Elixir 里,错误处理被当做一种 "code smell" 。我等你一小会儿,你再把这句话读一遍。

那么这是怎么做到的呢?还记得刚才我们谈到,Elixir 的 spawn 返回了一个 进程 ID 吧?它可不仅仅是用来发送消息的。它也可以被用来监视那个进程,并且检查它是否还活着。

因为进程太轻了,所以 elixir 里标准的的做法是创建俩。一个就是那个进程,另一个用来监控这个进程。这种方式叫做 supervisor 监控模式。Elixir 应用倾向于工作在监控树之下。监控者在后台使用另外一个方法 spawn_link 来创建进程,如果进程崩溃了,监控者就去重启它。

这里有个使用受监控的进程做除法运算的简单示例。

除0 的运算把进程搞崩了,supervisor 马上重启它,让后面的操作得以正常执行。用 supervisor 就简单直接的实现了这个功能。

相反的,Go 没有办法跟踪单个 Goroutine 个体的执行。错误处理必须在各个层次显示的进行,导致了许多这种的代码:

这里我们指望着在错误可能发生的地方都有错误处理,不论是不是在 goroutine 里面。

Goroutine 可能会将错误情形用同样的方式传给 channel。然而,如果 panic 发生了,每一个 Goroutine 都要保证满足自己的恢复条件,要不然整个应用程序都会崩。Panic 不等同于其他语言里的“异常”,因为 Panic 基本上都是系统级的 “stop everything” 的事件[17]。

内存溢出错误可以完美的概括这种情形,如果一个 goroutine 触发了一个内存溢出的错误,即使有着适当的错误处理,整个 Go 程序都会崩,因为内存状态是共享的。

Elixir 里,因为每个进程都有自己的堆空间,可以对每个进程设置堆大小,达到这个限制的话会挂掉这个进程,但然后,Elixir 会独立的垃圾回收那个进程的内存,然后重启它,而不会影响其他任何东西。

这不是说 Elixir 刀枪不入,VM 本身也可能会因为其他的问题内存溢出,但在进程里的话,这是个可控的问题。

这也不是在批评 Go,这是一个几乎所有的、使用共享内存的语言的通病 -- 意思是说,几乎每一个、你听说过的语言。这是 Erlang/Elixir 设计上的一个强项。

Go 的策略迫使在每一个可能出错的位置处理错误,这需要一个清晰的设计思想,并且可能促使一个经过深思熟虑的应用的诞生。

如 Joe Armstrong 所说,关键点在于,Elixir 模式是,你可以预期其永远不会停的应用程序。你也可以通过 suture library 手动实现一个 Go 版的 supervisor,因为你可以手动调用 Go 的调度器。

注意:在大多数 Go 的 Web server 里,panics 在 handlers 里已经被解决了。所以,一个 web 请求还没有严重到足以让整个应用挂掉的程度,不会的。当然在你自己的 Goroutine 里面还是得自己处理 panic。不要让我的陈述暗示 Go 很脆弱,因为它不脆弱。

  • 可变 vs 不可变

对于比较 Elixir 和 Go 来说,理解 数据可变 vs不可变 之间的权衡很重要。

Go 使用了多数程序员熟悉的内存管理风格:有共享内存,指针,可以被改变和重新赋值的数据结构。对于传递大的数据结构来说,这将会高效很多。

不可变的数据利用了 Copy-On-Write 技术。意思是说,在相同的栈空间里(Shawn: 同一个进程里),数据传递其实只是传了指向数据的指针而已,只有想改动那个数据的时候,底层才 Copy 一个副本出来让你改。

举个例子来说,一个由很多数据组成的列表,其实可能只是一个包含了 指向那些不可变数据的指针 的列表而已。而对这个列表排序,可能只是返回了一个顺序不同的指针列表,因为那些被指向的数据本身可以认定是不可变的。改变列表中的某个数据的值,将得到一个新的指针列表,其中包含了刚才被修改的那个数据的指针 (当然是个新地址了,原数据没动)。但是当我把这个列表传递给另外一个进程的时候,整个列表包括其中的值都会被 Copy 到新的栈空间。

  • 集群

由可变数据 vs不可变数据引出的另外一个问题是集群。

使用 Go 语言,只要你想,你是有办法实现一个无缝的 RPC 的。但是由于指针和共享内存,如果你调用一个远程方法,并传了一个指向本地资源的引用的话,它不会正常工作的。

在 Elixir 里面,所有的操作都是通过消息传递的。整个应用程序可以做成任意数量节点的集群。数据被传给一个函数,而这个函数返回一个新值。任何函数的调用都不会引起内存内的数据改动,这允许 Elixir 在调用不同栈空间里的函数、不同机器上的函数 或不同数据中心里的函数时,跟调用本栈空间里的函数的方式一模一样。

很多应用程序是不需要集群的,但是也有很多应用因为集群受益匪浅。比如聊天程序,用户可能连接到了不同的服务器上,他们之间需要通信。或者水平分布的数据库。这两种场景是 Phoenix 框架和 Erlang Mnesia 的常见应用案例。对于那些不使用产生瓶颈的中继节点的应用程序来说,集群是他们水平伸缩能力的关键。

  • 工具库

Go 有个 可扩展的标准库,让大多数开发者不用去找第三方库,几乎就能做任何事情。

Elixir 标准库 更精简一些,但它包含了更全的 Erlang 标准库 ,Erlang 标准库包含了三个预置的数据库 ETS/DETS/Mnesia,其他的软件包必须从第三方库拉取。

Elixir 和 Go 都有无数的第三方库可以用,Go 直接使用 go get 来从远程导入软件包,而 Elixir 使用 Mix。Mix 是一个编译工具,它用一种大多数编程语言的用户习惯的方式,调用 Hex 包管理工具。

Go 仍然在努力统一 整个语言范围内的包管理解决方案。在包管理解决方案 和 可扩展的标准库之间,只要能用标准库,多数 Go 社区的人似乎还是喜爱用标准库。已经有很多 包管理工具 可以用了。

  • 部署

Go 的部署简单明了。一个 Go 程序可以编译成一个单独的可执行文件,所有的依赖都包含在里面。然后可以本地化的执行。跨平台的,各种地方都能跑 (编译的时候预先指定目的机器的架构类型)。Go 的编译器可以为任何目标架构编译可执行文件,而不管你的当前运行 Go build 的机器是什么架构的。这是 Go 的大强项。

Elixir 其实有很多部署方式,但最主要的方式还是通过优秀的 distillery 工具来部署。它把你的 Elixir 应用以及所有依赖打包成一个可执行文件,然后可以部署到目标机器上。

两种语言的最大区别是,Elixir 编译环境的 架构类型 必须跟 目标机器的架构类型一样。官方文档里有很多关于这种情形的变通解决方案,但最简单的一种,是在一个跟目标机器相同的 Docker 容器环境里编译发布包。

有了上述两种方式,你可以直接停掉当前运行的代码,然后替换掉可执行文件,然后重启,就像多数今天的 Blue-Green部署方式那样。

  • 热更新

Elixir 还有 BEAM 带来的另外一种部署方法。这东西有点复杂,但对于某些特定类型的应用,好处非常大。叫做 “热加载” 或者 “热升级”。

Distillery 工具基于这个功能出发,简化了这个过程,你只需要在编译发布版时,在你的命令行里加上 --upgrade 标志。但这不代表任何情况下你都需要它。

在讨论什么情况下使用它之前,你需要先理解它是做什么的。

Erlang 最开始是给电话系统(OTP 的意思是 Open Telecom Platform) 使用的。目前这个星球上有近一半的电话系统是用 Erlang 的。Erlang 被设计为 “永不停止” 的语言。当有很多通话正在使用着你的系统的时候,“永不停止” 在部署的时候是个很复杂的问题。

如果不断开所有人的连接,你怎么部署呢?难道你需要先阻止所有新打进来的电话,然后礼貌的等待正在进行中的通话结束吗?

答案是不。这就是“热加载”的来由。

由于 Erlang 进程之间的栈空间隔离,升级可以不打断现有的进程。没跑起来的进程可以被替换掉,新启动的进程接收处理新的网络数据,并跟老进程一起并肩运行。正在跑的老进程可以一直干活,直到他们的工作完成。

这允许你再数百万通话正在进行的时候,部署升级。不打断老的通话,让他们自己结束。你可以想想一下你吹泡泡的时候,新的泡泡慢慢替代掉了天空中的老泡泡 。。。 基本上热更新也是这样子的原理,老的泡泡仍然在飞来飞去,直到破灭。

理解了这个东西,我们可以看一下热更新比较合适的潜在应用场景:

聊天程序。每个用户使用 WebSocket 连接到指定的机器上。

任务服务器。你希望在不影响正在运行的任务的同时做更新部署。

CDN服务器。在一个很慢的网络连接上,一个小的网络请求后面跟着一大堆正在进行的转发包。

拿 WebSocket 来说,这允许你更新一个可能有着百万活动连接的机器,而不会导致数以百万计的重连请求来轰炸服务器,也不会丢失正在发送中的消息。顺便说一句,这就是为啥 WhatsApp 是基于 Erlang 的。热更新已经被用于,在飞机飞行的过程中,更新航天系统。

缺点是,如果你需要回滚的话,热更新会更复杂。只有当你真的有场景需要它的时候,才应该用热更新。但是你知道你有办法做热更新,这挺好的。

集群也是一样,你不一定总是需要它,但是一旦用到了,它就是必不可少的。集群和热更新与分布式系统携手同行。

  • 结论

这篇文章很长,但希望它给了你一个对 Go 和 Elixir 之间差异的真切的轮廓。我发现一个最好的办法理解他们的差异:把 Elixir 想象成一个操作系统,而把 Go 想象成特殊程序。

在设计一个 特别快速、目的相当明确的解决方案时,Go 表现非常优异。Elixir 提供了一个环境,在其中很多不同的程序共存、运行、互相交流 (哪怕是在部署更新的时候)。你将用 Go 构建单独的微服务个体。你将在一个单独的 Elixir umbrella 项目里构建很多个微服务。

学习 Go 语言相对明确、简单些。Elixir 的话,你一旦掌握了其窍门儿,就非常直白了。但是如果你的目标是在使用之前全学完的话,浩瀚的 Erlang 和 OTP 的世界会挺吓人的。

两者都是在我的推荐清单里的优秀的编程语言,几乎可以做任何编程里的事情。

对于非常特定的代码,可移植的系统层级的工具,对性能敏感的任务、API, Go 很难被超越。对于全栈的网络应用,分布式系统,实时系统,和嵌入式应用,我将会使用Elixir。

希望对题主有帮助,耐心看到这里的朋友点个赞吧。

责任编辑:
热门阅读排行
© 16货源网 1064879863