被时间“埋没”的分布式王者:Erlang

被时间“埋没”的高并发王者:Erlang

分布式、fault-tolerant、高并发等等这些名词变得越来越火爆,一个新的框架、数据库、语言如果不跟这几个名词沾边,都不好意思摆在台面上。可是,早在20-30年前的通信领域已经出现了这样的需求,比如 容错、超高并发、在线代码更新、99.9%在线率等等。而互联网领域直到最近10今年才特别关注这种系统。

大概在90年代初,Erlang诞生了,它的设计者正式为了满足通讯领域系统高并发、高容错、分布式的需求设计Erlang和它的虚拟机,BEAM。(当然BEAM不是第一代虚拟机,而是经过几轮迭代后被大规模投入使用的版本)。

虽然,很多年轻的程序员可能没有通过Erlang,提起分布式、高并发,他们更多想到的可能是 Golang,Scala/Akka等等。可是Erlang系统就在我们身边:Cisco超过90%的交换机仍然在使用Erlang;早在2012年,WhatApp的生产系统就实现了在单个Beam虚拟机节点上同时处理超过2百万个TCP/IP连接;RabbitMQ 服务器端代码是Erlang实现的;电信公司T-Mobile的短信业务也是Erlang实现的;Ericsson用Erlang生态构建新的5G基础设施软件;等等。

可以说,在分布式、高并发、高容错这个领域,Erlang得到了广泛的应用,多年的使用也证明了Erlang生态在这个领域的可靠性。

我有幸和Erlang最初的几个设计者之一Robert Virding 学习过一段时间,了解了一些Erlang背后的故事。

Erlang的设计哲学

Erlang不是完美的语言,Erlang的设计具有鲜明的目的性:高并发、高容错、分布式。Erlang的虚拟机BEAM也是为了实现相同的目的,可以说,它只围绕这几个设计目的进行优化,比如它对Binary类型计算进行了优化,而浮点计算则非常缓慢。

Erlang本身属于函数式语言,没有变量,循环,一切都是Immutable等等。而 Erlang 的 Pattern Matching 能力非常优秀且高效。代码精简且清晰,同时为信息传递奠定了基础。最近十几年,工业界也开始慢慢接受、甚至推崇函数式编程,但是Erlang已经在30年前把函数式代码部署在了生产环境。

Erlang的并发模型属于Actor Model,它实现高并发的基石在于非常廉价的Process。这个Process不是操作系统中的进程,而是Beam虚拟机内部的一个抽象。Process非常廉价,每个进程只有2-4kb的foot print,一个Beam节点可以毫无压力的同事运行超过2百万个process。这些Process都是相互独立的,他们不共享任何内存,有独立的堆栈,除非建立连接,一个Process的崩溃完全不会影响到系统的其他进程。

更加有意思的是,这200万个虚拟机进程,仅仅需要运行在一个操作系统线程内。换句话说,BEAM把并发模型从操作系统中抽象出来了。这样做的优势显而易见:如果我们有多核处理器,相同的代码可以毫不费力的通过增加节点分布在不同的内核里,增加处理能力。

进程之间是通过传递信息实现互动,注意这些信息都是不可变的(immutable),同时信息的传递可以跨越节点,即可以实现不同内核中节点的通信、不同物理机器节点的通讯。事实上,代码中几乎不需要区分进程是local还是remote。这种跨节点通讯正式分布式系统的基础。

在BEAM进程、进行通讯以及函数式语法的基础上,为了进一步增加容错能力,实现99.9%系统在线率,Erlang也发展出了非常独特的 异常处理 和 代码更新 模式。BEAM虚拟机可以实现系统在线的情况下修复bug,并仅仅重新部署一部分出问题的系统。

大部分编程模式的异常处理都是基于 try-catch,也就是 Defensive Programming,即尽可能的捕捉异常,阻止程序崩溃。以为绝大部分系统不能接受线程异常,一个线程异常有肯能会导致整个系统崩溃。Erlang系统很不一样,因为所有进程都是独立的,一个进程的崩溃完全不会影响系统,甚至,系统中的大部分进程都不会意识到。所以,Erlang的 异常处理 更加专注于如何让仍然工作的进程修复崩溃的进程,也就是自我恢复,Self-healing。当然,Erlang生态并没有这个名词, 在Erlang 的世界里叫做 supervisor-tree。通常编写业务逻辑的时候,仅仅关注正确的情况,而不去主动捕捉异常,以为异常总是多种多样的,而且一定会发生。通常如果出现异常,进程就会终结,而他的supervisor进程会介入进行合理的操作,比如重启它等等。换句话说,把业务逻辑和异常处理逻辑分离。也正式因为这样的 异常处理和自我修复设计,实现了Erlang系统的高容错性能。

Erlang生态

总结一下 Erlang 实现高并发、高容错、分布式的基石:

  • 函数式语言,Immutability
  • 超轻量线程,Process
  • 自愈模式的异常处理,Supervision tree

无论是Erlang语言本身,还是他的虚拟机BEAM都是围绕上面那三个特征进行设计的。

其实,很多新的语言和框架都或多或少的在学习上面的特性,比如 go routine 和 channel其实就是轻量级线程和通讯,但却没有Erlang进程那么轻,而且共享内存。Erlang的独特之处在于它的设计目的很单一:高并发。而上面的三个特征是实现可靠的高并发系统必不可少的,Erlang把他们做到了极致,不妥协。

写在最后

正确的工具做正确的事情

Erlang 就是高并发场景的正确工具,他非常不完美,但是他满足了高并发场景所需要的工程特征,而且经历近30年的生产环境考验。