首页 > 代码库 > 硝烟中的Erlang -- Erlang生产系统问题诊断、调试、解决指南

硝烟中的Erlang -- Erlang生产系统问题诊断、调试、解决指南

英文原名:Stuff Goes Bad: Erlang In Anger

英文作者:FRED HEBERT

下载地址:http://vdisk.weibo.com/s/iGQ-rFuJU0-4

译者序


在我近20年的软件开发工作中,除了Erlang,还使用过许多其他编程语言。有工作需要的C/C++、Java,也有作为业余爱好使用的Lisp、Haskell、Scala等,其中我最喜欢的当属Erlang。除了因为我的电信软件开发背景外,还有一个很重要的原因是Erlang独特的设计哲学和解决问题方式。

 

大家听说Erlang,往往是因为其对高并发的良好支持。其实,Erlang的核心特征是容错,从某种程度上讲,并发只是容错这个约束下的一个副产品。容错是Erlang语言的DNA,也是和其他所有编程语言的本质区别所在。

 

我们知道,软件开发中最重要的一部分内容就是对错误的处理。所有其他的编程语言都把重点放在“防”上,通过强大的静态类型、静态分析工具以及大量的测试,希望在软件部署到生产环境前发现所有的错误。而Erlang的重点则在于“容”,允许软件在运行时发生错误,但是它提供了用于处理错误的机制和工具。借用本书中的把软件系统看作是人体的类比,其他编程语言只关注于环境卫生,防止生病;而Erlang则提供了免疫系统,允许病毒入侵,通过和病毒的对抗,增强免疫系统,提升生存能力。

 

这个差别给软件开发带来的影响是根本性的。大家知道,对于大型系统的开发、维护来说,最怕的就是无法控制改动的影响。我们希望每次改动最好只影响一个地方,我们通过良好的模块化设计和抽象来做到这一点。但是如果这个更改不幸逃过了静态检查和测试,在运行时出了问题,那么即使这个改动在静态层面确实是局部的,照样会造成整个系统的崩溃。而在Erlang中,不仅能做到静态层面的变化隔离,而且也可以做到运行时的错误隔离[1],让运行时的错误局部化,从而大大降低软件发布、部署的风险。

 

强大的并发支持也是Erlang的特色之一,在这一点上常常被其他语言争相模仿。不过,Erlang和模仿者之间有个根本的不同点:公平调度。为了做到公平调度,Erlang可谓“不择手段”,并做到了“令人发指”的地步[2]。为什么要费劲做这些工作呢?对于一个高并发系统来讲,软实时、低延时、可响应性往往是渴求的目标,同时也是一项困难的工作。尤其是,在系统过载时,多么希望能具有一致的、可预测的服务降级能力。而公平调度则是达成这些目标的最佳手段,Erlang也是目前唯一在并发上做到公平调度的语言。

 

由于Erlang在容错和并发的公平调度方面的独特性,可以说,这些年来Erlang一直被模仿,但是从未被超越。

 

从某种意义上讲,Erlang不仅是一门编程语言,更是一个系统平台。它不仅提供了开发阶段需要的支持,更提供了其他语言所没有的运行阶段的强大支持。其实,在静态检查和测试阶段发现的问题往往都是些“不那么有趣”的问题,那些逃逸出来的bug才是真正难对付的。特别是对于涉及并发和分布式的bug,往往难以通过静态检查和测试发现,并且传统的调试手段也无法奏效[3]。而Erlang则提供了强大的运行时问题诊断、调试、解决手段。使用Erlang的remote shell、tracing、自省机制以及强大的并发和容错支持,我们可以在系统工作时,深入到系统内部,进行问题诊断、跟踪和修正。甚至在需要时在线对其进行“高侵入性”的外科手术。一旦你用这种方法解决过一个困难的问题,你就再也离不开它了。如果要在静态类型和这项能力间进行选择,我会毫不犹豫地选择后者[4]。

 

对于Erlang存在的问题[5],提得最多的有两个:一个是缺乏静态类型支持,另一个是性能问题。Erlang是动态类型语言,往往会被认为不适合构架大型的系统。我自己也非常喜欢静态类型。一个强大的静态类型系统不但能够大大提升代码的可读性,而且还为我们提供了一个在逻辑层面进行思考、设计的强大框架,另外还可以让编译器、IDE等获取更深入的代码结构和语义信息从而提供更高级的静态分析支持。

 

不过,在构建大型系统方面,我有些不同看法[6]。如果说互联网是目前最庞大的系统,相信没有人会反对。那么这个如此庞大的系统能构建起来的原因是什么呢?显然不是因为静态类型,根本原因在于系统的组织和交互方式。互联网中的每个部件都是彼此间隔离的实体,通过定义良好的协议相互通信,一个部件的失效不会导致其他部件出现问题。这种方式和Erlang的设计哲学是同构的。每个Erlang系统都是一个小型的互联网系统,每个进程对应一台主机,进程间的消息对应协议,一个进程的崩溃不会影响其他进程……。Erlang所推崇的设计哲学:crash-oriented以及protocol-oriented,是架构大型系统的最佳方式[7]。

 

当然现在,你鱼和熊掌可以兼得,Erlang已经支持丰富的静态类型定义和标注功能[8],并且可以通过Dialyzer工具进行类型推导和静态检查。

 

再来说说性能问题。在计算密集型领域,Erlang确实性能不高[9]。因此,如果你要编写的是需要大量计算的工具程序,那么Erlang是不适合的。不过,如果说涉及计算的部分只是系统中的一个局部模块,而亟需解决的是一些更困难的系统层面的设计问题:并发、分布式、伸缩、容错、短响应时间、在线升级以及调试运维等等,那么Erlang则是最佳选择。此时,可以用Erlang作为工具来解决这些系统层面的难题,局部的计算热点可以用其他语言(比如C语言)甚至硬件来完成。Erlang提供了多种和其他语言以及硬件集成的方法,非常方便,可以根据自己的需要(安全性、性能)进行选择。

 

我前段时间曾经开发过一款webRTC实时媒体网关[10],就是采用了Erlang +C的方案。其中涉及媒体处理的部分全部用C编写,通过NIF和Erlang交互,系统层面的难题则都交给Erlang完成。系统上线几个月,用户量就达到数百万。期间,系统运行稳定,扩容方便,处理性能也不错(尤其是高负载时的服务降级情况令人满意)。不是说使用其他语言无法做到,不过要付出的努力何止10倍[11]!

 

目前市面上关于Erlang的书籍也有不少,这些书几乎都把重点放在基本的Erlang语法和设计方法上。基本上没有书籍专门讲解如何解决Erlang生产系统中出现的疑难问题(比如,过载、内存泄漏和碎片、CPU过度占用等)[12]。本书则可以说是填补了这方面的空白。

 

作者Fred Hebert在构建和运维大型Erlang系统方面经验丰富,而且擅长写作[13]。在本书中,他不仅把自己多年实战中积累的设计、问题诊断、调试以及解决的经验分享给大家,而且也对Erlang的内存和调度器工作原理及其强大的运行时监控和自省能力做了深入的分析和介绍。更难能可贵的是,他还把自己解决问题的方法工具化,便于大家使用。

 

在阅读本书之后,读者可以对Erlang的设计哲学和虚拟机工作机制有更深层次的理解。掌握了书中的知识和工具,虽然不能保证在运维Erlang生产系统时高枕无忧,但是至少在面对困难时知道从何处下手、如何收集数据、如何定位问题,为最后真正解决难题打下坚实的基础。

 

最后,如果发现译文中有任何问题,欢迎来信指正(dhui@263.net)。希望大家阅读愉快!



邓辉

2014.11于上海



[1] 借助操作系统的进程也可以做到运行时的错误隔离,不过粒度太大,也过于重量。

[2] 为了能够做到跨OS的高效调度,Erlang放弃了基于时间片,采用了基于reduction的方式。几乎在系统的每个地方都会进行reduction计数,来达到公平调度的目的。

[3] 比如一个和竞争有关的bug,一旦加上断点,竞争可能就不会出现了。

[4] 其实可以兼得,Erlang现在已经支持类型定义、标注和推导。

[5] 那些主观性太强的我们不讨论,比如,有人觉得Erlang语法怪异。

[6] 这些看法只针对Erlang。对于其他的动态类型语言,在程序规模变大后,确实有难以理解和维护的问题。在Erlang中,由于其let it crash哲学,很多动态类型语言的问题可以在很大程度上被避免。

[7] 目前火热的micro-service架构某种程度上和Erlang的哲学类似。

[8] 无论如何,给程序加上类型标注都是一项好的实践。

[9] 这方面的性能大概是C语言的1/7左右。

[10] 主要作用是完成浏览器的webRTC媒体流和IMS网络媒体流的之间的互通,需要大量转码和控制

[11] 这个是我自己的对比数字,我曾经用C++开发过类似的系统。

[12] 一方面是因为大部分系统其实没有那么庞大复杂,只要按照基本的Erlang原则进行设计开发,一般就不会出现什么问题;另一方面,有经验、有时间并且能写好的人确实也不多。

[13] Fred Hebert也是Learn you some Erlang for great good一书的作者。


硝烟中的Erlang -- Erlang生产系统问题诊断、调试、解决指南