了解Erlang
是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。Joe Armstrong 开发的。
初步认识:
erlang 世界的基石,了解下面的,就可以对 erlang 有一个简单的认识:
- everything is a process.
- process are strongly isolated.
- process creation and destruction is a lightweight operation.
- message passing is the only way for processes to interact.
- processes have unique names.
- if you know the name of a process you can send it a message.
- processes share no resources.
- error handling is non-local.
- processes do what they are supposed to do or fail.
process(进程)
和 OS 的 process 概念几乎一样,就是 PCB(Process Control Block), 以及围绕 PCB 的 heap,stack,mailbox 等内存空间。只不过,Erlang 的 process 的 memory footprint 很小很小,一个刚初始化的 process,也就占用几百个 words。Erlang 的 process 源自 actor model 的思想,是常见的 concurrency model 的一种。
routing(路由)
当系统中有了无数独立的 process 之后,一个迫切的问题是如何找到我们需要与之胡操作的 process。这就是路由的过程。 1. 通过唯一的 pid 来定位到目标 process; \2. 通过和 pid 一一对应的名字来定位到 process; 3. 通过路由来寻找,这个叫做 name registry。
error handling(错误处理)
靠 process 之间的 link 完成的。link 是一个作用域双方互相监视的机制。link 是双向的连坐机制。对于有些影响不大的问题,并不需要如此强有力的手段,于是有了 monitor。
Erlang/OTP 的 supervision tree 就是基于link/monitor 这样简单的机制构建起来的,而之所以 erlang 可以任性的 let it crash。其中很大一个原因就是 process 非常轻量,构建和销毁的代价无非就是创建和回收若干个 C structs。
concurrency(并发)
Erlang 的并发模型使用了 actor model。就是说每个 actor 和其他 actor 通讯的方式能且仅能通过 message passing 而完成。因而,每个 actor 有其独立的 mailbox,用于接收消息。
为了调度数量庞大的 processes,erlang VM 有自己的 scheduler — 在 SMP (简单理解为多核)环境下,每个 CPU core 有一个 scheduler。和 OS 的 scheduler 类似,有 run queue 和 wait queue,scheduler 从 run queue 上取出下一个 process,执行其代码。如果 process 没有通过诸如 receive message 这样的动作显式地将自己阻塞并被调度出去,scheduler 会再一个固定的 cycle 后把 process 调度出去。
所以从 erlang 程序员的角度来看,erlang 是 preemptive scheduling,并且,这种每个 process 获得的 CPU 时间几乎固定,时间在可预知的时间内得到调度。成为 soft realtime。
Hard realtime:CPU 的时钟终端。会再固定的间隔被触发,丝毫不差,风雨无阻。
进一步了解:
Message passing(消息传递)
处理 binary 的方式,大的 binary(通过 binary allocator分配出来的)放在 share pool 里,发消息只发一个 reference,对方收到以后,通过 reference 可以直接找到数据,避免无谓的拷贝。这个binary 要记录 refcnt,对「收看」和「看过的」行为要记录,保证 binary 不会在 consumer 还未使用前就被释放,也要保证不会再全部使用者使用完了之后还未释放。
scheduling(调度)
Cooperative multitasking:调度的控制权交给各个 process,相信「人性本善」。使用一些 CPU 后就会主动做 context switch。调度的时间是可预知的(因为 process 自己的代码去 yield),实现起来简单,但很难保证每个 process 对时机把握的尽善尽美,一旦有不合理实现的 process,通过无休止的大循环占用过多的 CPU,就会把其他 process 饿死。
Preemptive multitasking:前者的改进。调度的控制权把握在 OS 手上,系统唯美个 process 的 CPU 使用时间设定一个上限,通过 CPU 时钟中断触发 scheduling,如果当前 process 达到这个上限后会被调度出去,这种方式调度的时机不可预知,可可能发生在 process 代码的任何位置,所以实现起来,尤其是 context switch 会复杂一些。
- 从erlang application 角度来看,erlang VM 的调度是 preemptive 的,因为每个 erlang process 不知道耶不用关心自己会在什么时候被调度;
- 从BEAM scheduler 实现的角度来看,又是 cooperative 的,每个 process 的 main 函数(
process_main
)里会显式调用erts_schedule
,看看自己的 reduction count 是否耗尽,如果是,做一个 context switch。
reduction 是 erlang 用于检查 process throughput 的工具。类似 linux 的 nice value。一次 reduction 可以简单认为是一次函数的调用。
erlang VM 的 scheduler是标准的 scheduler:每个 core 上一个逻辑意义的 scheduler,有多级队列,按照 process 的优先级区割。max 是 erlang 系统内部保留的优先级,往下是 high / normal / low,按这个顺序依次调度。
Timer / timeout 这样的 event 如何处理的?使用 timer wheel。
OTP
全称是 Open Telecom Platform。
如果把 erlang VM 看作是 OS 的 kernel,那么 OTP 就是这个 kernel 的前天组成部分:stdlib,initd, syslog,OAM,等。
gen_server
:通用服务器行为。gen_fsm
:gen_server
的实现,处理有限状态机。gen_event
:gen_server
的实现,处理事件。supervisor
:负责启动、停止和监视它的子进程。基本思想是它要保持它的子进程有效,必要的时候可以重启他们。application
:类似于组件。
release
一个 release 包含若干个 application,每个 application 的版本,之间的依赖关系,启动顺序,需要加载的模块,都在 release 文件中说明。
- 如何以 daemon 方式运行;
- 如果以 daemon 方式运行,如何 attach 进行,便于调试;
- 如果在不同的机器上把你的软件练成一个 cluster;
- 如何在 release 之间 upgrade/downgrade;
缺点
- 学习曲线陡峭。
- immutable 引入的无处不在的内存拷贝,拖累了整个系统的效率。