BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

《Elixir in Action》书评及作者问答录

| 作者 Sergio De Simone 关注 13 他的粉丝 ,译者 邵思华 关注 3 他的粉丝 发布于 2015年9月30日. 估计阅读时间: 18 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

《Elixir in Action》是由Manning所出版的一本新书,本书为读者介绍了Elixir这门语言以及Erlang虚拟机,同时也讨论了与并发编程、容错以及与高可用性相关的话题。InfoQ有幸与本书的作者Saša Jurić进行了一次访谈。

《Elixir in Action》的内容源自于Jurić在Erlang方面的经验,他为此特意创建了一个博客,为来自面向对象背景的程序员展现Erlang的优势。Jurić之后转而使用Elixir,这是一种函数式的并发编程语言,它的目标是提供一种比起以Prolog为启发创建的Erlang更简便的语法,以及更高等的抽象能力。

本书的内容采取了一种渐进式的方式,首先介绍了Elixir的语法与它的基本特性,例如宏、模式匹配、模块、多态等等,随后介绍了如何创建一个容错的、高可用的、并发的分布式系统。在全书的一些核心章节中涵盖了Erlang平台的大量知识,其中的主题包括管理进程、持久化、通过supervision树进行运行时错误处理的多种方式,以及“任其崩溃”(let it crash)等设计哲学。

总的来说,《Elixir in Action》一书不仅为如何使用Elixir语言与Erlang VM,同时也为读者如何迈进高可用性系统这一领域打下了坚实的基础。InfoQ与Saša Jurić进行了一次访谈,以了解这本书背后的更多知识。

InfoQ:是什么原因促使你编写了本书,它与现有的一些针对Elixir的书籍又有何不同之处呢?

Saša《Elixir in Action》的目标是帮助那些具有编程经验,但缺乏Erlang、Elixir或函数式编程背景知识的程序员开发能够在生产环境中运行的系统。在这本书的构思阶段,我就假设本书的大部分读者都来自于面向对象语言的背景。对于这些读者来说,书中的许多内容都是新颖的:新的语言、运行时与生态系统,包括函数式编程与类似于Actor的并发编程思想。读者需要打下大量的知识基础,而这可能会令人望而生畏惧。

我相信,通过一种更为专注的方式,初学者的学习过程将能够得到极大的简化。因此,我并不打算编写一本完整的参考书,而是决定专注于那些对于多数目标读者来说有些不寻常的核心概念:函数式编程、并发,以及OTP框架的核心思想。我相信一旦读者开始对这些主题感觉到“心意相通”,他们在学习本书并未叙述的一些内容时就会更容易。举例来说,如果读者掌握了Erlang中的并发知识,并且理解了大多数最重要的OTP概念(GenServer、Supervisor、Application),那么他们就能够较容易地自行学习其它的抽象概念,例如Task、Agent或GenEvent。

我相信,这是目前唯一一本以这种方式进行撰写的Elixir书籍。任何一位想要使用Elixir/Erlang技术打造可伸缩的、高容错性的分布式系统,都必须学习Elixir in Action一书中所介绍的各种材料。当然,你也可以通过其它资源学习这些内容,但我认为,本书是目前唯一一本面向Elixir介绍以上所有主题的书籍。

InfoQ:你能否简单地介绍一下你在Elixir方面的经验?它对于你实现工作目标提供了哪些程度的帮助?

Saša对我来说,Elixir最重要的一方面实际上是Erlang VM,这是一切功能的基础,这也是我首次接触Erlang时对于我帮助最大的内容。大约5年以前,我当时需要实现一个基于长轮询的服务器推,它需要不断地将频繁变化的数据传输给数以千计的连接用户。经过一段时间的评估之后,我们最终选择了Erlang,我从中也收获了许多经验。Erlang以一种结构化的方式帮助我克服了重重阻碍:使用它创建解决方案的过程非常顺利,即使我对它的开发平台还有些陌生。最终设计出的系统具备了高伸缩性、高效性以及灵活性。我能强烈地感觉到Erlang在支持着我,即使我犯下了各种错误。这套系统能够处理各种预料之外的状况,而我甚至还没有在这方面做过什么计划。

InfoQ:Elixir特别适合于哪些类型的项目?

Saša在我看来,Elixir/Erlang适合于任何类型的服务端系统:即需要持续运行,并且尽可能持续提供服务的软件。这方面一个很明显的示例就是基于web的系统,用于处理传入的HTTP请求,但也需要进行其它活动,例如后台的、周期性的作业或缓存管理。在这种系统中,许多活动在某个具体时间往往处于挂起状态。而Erlang应对这种并发情况的途径让开发者的担子轻了许多。如果每个独立的活动都能够分配至少一个独立的Erlang进程(不要与OS的进程混淆了),我们就不仅能够实现可伸缩性,还能够改善容错性:一个单一的进程故障不会对整个系统的绝大部分产生影响。并且还有许多方法能够检测到这种故障,然后从故障中恢复。

我发现这种处理方式非常直观,并且任意一种类型的后端系统都能够从中受益。我看到某些观点说Erlang只适合于大规模系统,或某些特定的领域,例如电信领域。我不同意这种观点。Erlang能够帮助系统实现大规模化,而这种特质在任何类型的生产系统中都是必要的,无论其规模与领域如何。因为如果某个系统做不到高可用 ,那么它就会频繁地产生故障。即使你不需要实现传说中的9个9的可用性,但你也应当希望让你的系统停机时间尽量减短吧。实现这一点是一个艰难的挑战,而Erlang则能够帮助你实现这个目标。

InfoQ:你怎样描述Elixir与Erlang两者之间的关系呢?

Saša:我的看法是,Elixir是在Erlang与OTP所提供的强大基础之上所扩展的能力,旨在提升开发者的生产力。我曾经进行过大量的全职Erlang开发工作,虽然我十分热爱这门语言,但有许多任务也显得过于繁琐,我不得不无谓地处理一些低层次的机械性工作。而Elixir在这方面提供了大量实用的特性,包括语言(例如通过宏进行元编程,以及通过协议实现多态)与工具(例如构建项目的多种工具搭配,以及十六进制包管理器)两方面,这让我们能够专注于处理一些更为实际的问题。

我个人的感受是使用Elixir进行开发比起使用纯粹的Erlang进行开发要简单许多。Elixir使可伸缩性与开发者的生产力之间这种刻意的权衡大大降低了,甚至是完全消除了。仅仅因为某个开发平台允许我们创建高并发、可伸缩、高容错的分布式系统,并不代表它就应当难以使用。同时,Elixir的运行时并没有彻底远离Erlang的哲学。作为一种函数式语言,它的语义与Erlang非常接近。Elixir能够无缝地集成各种Erlang库,因此开发者能够完整地访问整个Erlang生态系统。

InfoQ:在决定直接使用Erlang或Elixir时,这两者有哪些缺陷是开发者需要考虑进去的?

Saša我不认为它们有任何的缺陷。它们所具有的优势主要来自于VM本身,以及已经过充分测试的OTP框架,而你可以从其中任何一门语言中收获这些益处。因此主要的决定因素在于其它的一些附加价值。Elixir加入了一些额外的特性,因此实际上它是一门比起Erlang更加复杂的语言。而这门语言的优势在于它的代码更加简洁,在样板代码方面的负担较少。与之相比,Erlang是一门更为简单的语言,因此所涉及的代码更多,但它也因而显得更为明确。

从我个人来看,Elixir在样板代码的减少与语言的明确性方面找到了一个很好的平衡点。它没有Ruby等语言表面上那么神奇,而仍然提供了各种实用的特性,其中最值得留意的就是元编程与多态性。

InfoQ:创建一个高可用、高容错的并发系统是一项复杂的任务,尤其是从CAP定律的角度来看。要实现这一目标,选择一门合适的语言与运行时环境的重要性有多高?

Saša:这实际上取决于个人的观点。人们在各种语言上都实现过大规模的系统,因此不用Erlang也是完全可能的。不过对我来说,问题不仅仅在于是否可能,还在于某个工具能够在多大程度上帮助我们完成这一过程。毕竟,工具的目的就是为我们提供服务。

而这也是为什么我很看重Erlang的一个原因。在我看来,它为系统化地处理编写高可用系统所面临的挑战提供了简单而又非常强大的构建块。它的主要工具是Erlang进程,它让我们能够将工作分解为几千个,乃至上百万个独立的部分。通过使用多个进程,我们就获得了可伸缩性与容错性。它的崩溃传递机制能够让我们有机会处理这些故障:如果某个部分崩溃了,系统中的其它部分会收到它的通知,并进行相应的处理。最后,无共享并发机制能够实现分布式系统,即使在一台独立的机器上也不例外。其实在本质上,通过将整个工作分解为大量隔离的、完全独立的实体(进程),我们已经实现了分布式工作。当然,将系统在多台机器上实现集群仍然不是一件简单的事,毕竟分布式系统在本质上就存在着复杂性。但至少Erlang已经为我们解决了一些低层次的工作,我们可以始终利用相同的基元功能实现协作,即进程与消息传递。因此我们就能够专注于业务上内在的挑战,而不是将大量的精力消耗在低层次的细节上。

总的来说,我认为Erlang能够简化实现高可用性的挑战。你也可以使用Erlang以外的技术应对这些挑战,但很可能会因此付出更多的努力。

InfoQ:Erlang的一个核心思想是使用非常轻量级的进程模型,这使得上下文切换的开销非常低。另一方面,在许多系统中仍然用线程处理可伸缩性,而线程的可伸缩性往往会成为系统的瓶颈。为了避免这种瓶颈,可以使用一个完整的异步模型配合一个小型的线程池,这种做法也有成功案例(这里有一个参考示例)。你能否详细地分析一下Erlang的处理方式与完整的异步方法相比所具有的优势?

Saša我认为Erlang为我们创建高并发的系统提供了一种优秀而整洁的抽象,而任何一种需要持续处理各种不同任务的系统在本质上都是并发的。Erlang的处理方式非常适合于这种类型的问题,你总是可以通过进程来应对各种类型的任务,无论是I/O密集型还是CPU密集型任务,并且你可以信任VM会高效地分派工作。在使用Erlang不太会出现许多顿悟的情况,也不太会搬起石头砸了自己的脚。它能够让减轻我们的负担,让我们专注于实际的业务问题。

反之,如果你打算自行设计一种线程池,那么就不得不自己处理许多问题。打个比方,如果你在某个线程中执行一个时间很长的计算,那么你会阻塞在同一个线程上挂起的其它活动。如果某个线程因为一个bug而产生故障,该线程上运行的所有活动都会失败。这一点当然是能够解决的,但你或许要为投入大量的时间,以实现一种类似于Erlang VM的功能。既然如此,为什么不依赖于某个已被证实的解决方案呢?如果单纯的处理速度或是内存占用确实极端重要,那么采取自定义的实现方案可能还有一些益处,但在我所遇到的这些情形中来看,基本都不属于这种情况。

InfoQ:Erlang的另一个基本原则也被Elixir保留下来了,那就是“任其崩溃”。这种做法目前已经演变为将例行公事般地干掉进程作为一种确保系统能够容忍这一事件的手段了。这种策略对于打造一个具备容错性的Erlang/Elixir系统有多大的重要性?

SašaErlang设计的一个前提就是在生产环境中的系统有可能产生错误,但系统作为一个整体不能够中止:它应当尽力保留所有的服务,并尽快地从故障中进行自我修复。

任其崩溃在这种场合扮演了一个核心角色,它是一种简单的技术,能够让我们以一种有条不紊的方式处理系统的错误。在这种情形下,我们会选择让进程崩溃,并依靠Supervisor修复问题。这种做法的好处是该进程的主体代码可以不必操心错误处理的问题,例如编写try-catch或“if err != nil”这样的代码块,而只关注主路径上的逻辑。我们甚至还可以通过模式匹配的方式优雅地对各种期望进行断言。

在我看来,这种方式比try-catch-ignore的做法更优秀,因为一旦进程中止,它的状态也就消失了。而问题的根源很可能来自于有问题的状态。在进程重启之后,新的进程会生成全新的、稳定的状态,因此进程能够再次运转。至少它可以稳定地运行一段时间,直到状态再次出现问题为止。这种做法能够让系统中有问题的地方浮现出来,多数服务在这种方式下都会表现出偶尔的故障现象,直至问题的根源解决为止。

与任其崩溃相辅相成的一点是通过Supervisor进行恢复。如果你建立了一个细粒度的supervision树,那么所需重启的部分也相对较少。一旦产生故障,你可以试着重启系统中的一小部分如果问题仍然没有解决,你可以逐渐增加这部分的区域,直到系统中有问题的那部分被重启为止。相反,如果你采用了try-catch-ignore方式,就有可能导致错误的状态始终延续,最终产生了一个永无休止的故障循环。

InfoQ:“任其崩溃”是仅属于Erlang的一种独特功能吗?可否将其移植至其它不使用Erlang VM的环境中呢?

Saša问得好!首先我要强调一点,OTP是用纯粹的Erlang构建的,它依赖于Erlang VM的基础功能。理解这一点非常重要,因为我曾经看到过一些说法,认为OTP能够以某种方式“移植”到其它运行时环境中。但我认为这是不太可能的,除非目标运行时平台能够提供一些严格的保障。

具体到任其崩溃和supervisor来说, Erlang的VM为它们提供了一些重要的保障。

  1. 每个进程的状态都是私有的,一旦进程中止,它不会留下任何垃圾状态,从而也不会干扰其它的进程。
  2. 当一个单一的进程崩溃时,其它进程不会受到影响,它们的运行不会被打断,除非你有意这么做。
  3. 其它进程能够收到某个进程崩溃的通知,并进行一些相应的处理。
  4. 可以无条件地中止一个进程(即使是进程正在进行一个密集的CPU运算)。
  5. 进程可以拥有外部资源(例如文件句柄或socket),一旦进程中止,它所拥有的资源会自动回收。

前两点特性能够帮助我们将故障的后果局限在一定范围内:如果有部分出现问题,整个系统的大部分依然能够继续提供服务。第三点保障能够让我们对某个故障进行响应,当发生崩溃时,Supervisor能够对其进行纠正。最后两点保证了适当的系统清理,如果没有这两点保障,系统可能会产生孤儿进程或是资源无法释放的问题。

如果缺乏这些保障,我认为是无法实现Erlang的容错性能力的。即使你能够尽力接近,但永远也做不到100%的功能,总会有些隐秘的功能是你无法察觉的。这并不是说你必须要使用Erlang VM才能够实现任其崩溃的做法,只是说你需要一种能够提供这些保障的VM。

InfoQ:你是否能够分享一下你对于Elixir目前在业界的使用情况的展望?

Saša:虽然Elixir还是一门新生的语言,但它的基础(Erlang)已经非常稳定,其能力近20年来在各种不同的大型系统中都得到了证实。成功的案例包括WhatsApp、RabbitMQ、Riak、实时竞价(AdRoll),以及财务系统(Klarna)等等。至于Elixir,我已经看到它越来越多地出现在各种解决方案的生产环境中,例如游戏的后台物联网(IoT)。可以在这里找到在生产环境中使用Elixir的公司的一个列表,我很期待看到它今后的发展。

关于本书作者

Saša Jurić是一位软件开发者,他在使用Elixir和Erlang打造高负载、高并发的服务端系统方面具有丰富的经验。

查看英文原文:Elixir in Action Review and Q&A with the Author

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

深度内容

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT