1 OTP的前世今生
Erlang出现在20世纪80年代中期,由爱立信(Ericsson)所管辖的实验室所开发。当时那些大神级人物为了寻找适合下一代电信产品的编程语言,花费了两年多的时间,使用原型法测试了所有可能的编程语言,最终发现,虽然当时现有的编程语言也有一些有趣和相关的特性,但是并没有一门独立的语言能包容电信行业所需要的所有特性,这一点让他们非常不爽,于是决定开发一种全新的编程语言,这就Erlang。
到了1996年,Erlang编程框架OTP诞生了,OTP给Erlang编程语言系统带来了结构化的框架,以及一套实现健壮性和容错性的工具和类库。时间到了1998年12月,爱立信决定将Erlang作为开源代码发布。OTP最初是开放电信平台(Open Telecom Platform)的缩写,是为了构建和运行电信系统而设计的一个开发系统。开源之后,已经没有人稀罕它在电信方面的品牌效应了,发展至今,无论是Erlang还是OTP,都早已不再局限于电信应用,OTP现在更为贴切的名字应该是"并发系统平台"。
Erlang从一个完整的,独一无二的世界观开始,提供了一个如操作系统级细密的 VM,随后又将业界的最佳实践抽象出一套框架——OTP,解决了很多分布式并发系统下复杂的基础问题。OTP接近于应用程序基础系统,包含了一组库和实现方式,可以用来构建大规模、容错和分布式的应用程序。标准的Erlang分发套装就包含OTP库,可以说OTP基本上是与生俱来的。OTP基本上涵盖了系统生产的方方面面,它不单单关心软件如何撰写,还关心软件如何运行;不仅仅关心软件今天如何运行,还关心软件明日如何升级。
2 OTP的可靠否
写了大半天的云笔记,不知什么原因,它竟然自动同步到几个小时以前的状态,在这里再次痛恨一切不可靠的东西!
当今的互联网世界,所有人都在追捧和创造新的技术、新的名词,但是回归到问题本身,新的技术一定好吗?选择合适和可靠的技术才是王道。而一个技术是不是可靠,往往需要多年的大规模生产验证。
虽然,很多人可能并没有听说过Erlang,可是Erlang系统就在我们身边:
- Cisco超过90%的交换机仍然在使用Erlang;
- 早在2012年,WhatsApp的生产系统就实现了在单个Beam虚拟机节点上同时处理超过2百万个TCP/IP连接;
- RabbitMQ 服务器端代码是Erlang实现的;
- 电信公司T-Mobile的短信业务也是Erlang实现的;
- Ericsson用Erlang生态构建新的5G基础设施软件;
- 等等。
可以说,在分布式、高并发、高容错诸多领域,Erlang早得到了广泛的应用; 而OTP作为Erlang的重要组成,多年的使用也证明了它的可靠性勿容置疑。
3 为什么要有OTP
先来看2段亲切而熟悉的代码:
#include<stdio.h>
int main(void){
printf("Hello!\n");
return 0;
}
package com;
import java.io.*;
import java.util.*;
在常见的语言中,所谓的库,往往是语言的一种扩展,编译过后,它可能会变成程序的一部分,或者是在运行时可公共调用的一部分。程序运行中的艰难险阻,需要我们自已去面对。
而Erlang处理这个问题的哲学思想不同,它是从宏观上把构建系统的公共特性进行提炼,形成一个公共的规范,当然也帮我们把容易出问题的基础部分全部实现完成,我们只是需要将个性化的部分实现即可。这么做的好处在于问题的非函数部分(比如如何进行实时代码升级)对所有应用程序都是一样的,而函数部分(由回调函数提供)在每个问题里都是不同的。
这正是编程中人们高度期望的一种境界——“困难”的程序被隔离成了系统中的一些定义良好的小的部分,系统中绝大部分代码能够用有着良好类型定义的顺序化程序来编写。
4 OTP的behaviour
OTP当中提供了大量的工具模块来帮助我们完成日常开发工作,同时该平台抽象了大量的behaviour,例如常见的状态机,通用服务器,进程监控以等。 behaviour不单单加快了我们的开发工作,同时也提高了整个系统的稳定性和可扩展性。你可以把OTP中behaviour看作是一个用回调函数作为参数的应用程序框架,本身就能提供容错性、可扩展性和动态代码升级等属性。 简单地说,behaviour负责解决问题的非函数部分,而回调函数负责解决函数部分。
OTP将常见的工作进行高度提炼为behaviour,基于下面原因:
- OTP 系统中实现 behaviour 的通用模块是有专家 Erlang 编程人员编写的。这些模块都是建立在许多年的经验的基础上的,代表了编写代码来解决某些特殊问题的“最佳实践”。
- 使用 OTP 的 behaviour 来构建的系统拥有非常有规则的结构,例如, 所有的客户-服务器和监督树都有着同样的结构。使用 behaviour,就会迫使解决某一问题时采用公共的结构。应用程序员只需要提供定义他们的特殊问题的语义的代码,而所有的基础设施都由 behaviour自动提供。
- 对于加入已经存在的团队的一个新程序员来说,基于 behaviour 的解决问题的方式更容易理解。只要他们熟悉了 behaviour,他们就能够很轻易地识别出哪种情况下应该用哪种 behaviour。
- 系统编程中大部分的“复杂问题”都被隐蔽在了 behaviour 的实现中(这些复杂的问题实际上比我们这里描述的还要复杂得多)。如果有机会仔细分析各个behaviour,你会发现所有处理并发、消息传递等等事务的代码都被隔离在了 behaviour 的“通用”部 分,而“问题相关”的代码都是一些有着良好的类型定义的纯顺序化函数。
在OTP中,主要的behaviour有:
- gen_server——用来构建在客户-服务器模型中使用的服务器程序。
- gen_event——用来构建事件处理器程序。
- gen_fsm——用来实现有限状态机。
- supervisor—— 用来实现监督树。
- application——用作打包整个应用程序的容器。
在具体实现过程中,我们实现行为模式接口所规定的代码即可,这样就可以充分发挥出模式容器的能力。使用OTP的behaviour可以带来下列好处:
- 开发者用更少的代码完成更多的事情,有时可以大大缩减代码量
- 代码坚如磐石,稳定可靠,因为其核心库代码经过了严酷的测试
- 代码可以被嵌入到更大、更强劲功能的OTP框架,例如监督树
- 代码更易于的于理解,因为遵循的是众所周知的模式
5 gen_server
behaviour是面向进程编程中各种常见模式的一种形式化表述。比如,服务器这个概念就非常通用,你所编写的进程之中很大一部分就符合这个概念。这些进程有很多共通之处,每出现一种新的服务器进程就重新编写一遍这类代码是毫无意义的,这样做还会到处引入各中琐碎的bug和细微的差异。gen_server是OTP中最常见也是最实用的行为模式。
gen_server进程实际是一个循环去阻塞,接取消息,处理消息回应,返回下一次循环参数的过程。不同于死循环,gen_server进程在阻塞期间并不消耗CPU,这是erlang虚拟机调度实现的,只有当消息队列收到消息后,调度器会按消息顺序分配资源给进程去执行,根据消息的类型调用相应相应的回调函数处理,下面来看看接口规定是啥模样:
gen_server module Callback module Desc
----------------- --------------- ----------------
gen_server:start
gen_server:start_link -----> Module:init/1 启动gen_server
gen_server:stop -----> Module:terminate/2 终止gen_server
gen_server:call
gen_server:multi_call -----> Module:handle_call/3 gen_server同步调用,
gen_server:cast
gen_server:abcast -----> Module:handle_cast/2 gen_server异步调用
- -----> Module:handle_info/2 gen_server进程默认方式接收到消息的处理
- -----> Module:terminate/2
- -----> Module:code_change/3
6 gen_event
在OTP中有通用事件管理器进程,该进程可动态添加和删除的任意数量的事件处理程序。事件可以是例如错误,警报或要记录的某些信息。 gen_event行为主要用于生成event进程,该进程接受事件消息,并根据事件消息做对应的事件处理,而提供的对应事件处理其实就是添加的“回调函数”。
gen_event函数库 回调函数 描述
---------------- --------------- -----------------
gen_event:start
gen_event:start_monitor
gen_event:start_link -----> - 启动gen_event事件管理器
gen_event:add_handler 增加gen_event事件处理器
gen_event:add_sup_handler -----> Module:init/1 增加gen_event事件处理器, 通过链接来监督连接和调用过程
gen_event:notify 异步触发事件, 会到所有事件处理器中匹配
gen_event:sync_notify -----> Module:handle_event/2 同步触发事件
gen_event:send_request
gen_event:call -----> Module:handle_call/2
- -----> Module:handle_info/2
gen_event:delete_handler -----> Module:terminate/2 删除事件处理器
gen_event:swap_handler 替换事件处理器
gen_event:swap_sup_handler -----> Module1:terminate/2 替换事件处理器, 通过链接来监督连接和调用过程
Module2:init/1
gen_event:which_handlers -----> - 返回事件管理器中所有事件处理器的列表
gen_event:stop -----> Module:terminate/2
- -----> Module:code_change/3
7 supervisor
在 Erlang/OTP 中有一个基本概念叫监督树,这是一种建立在督程与佣程思想上的进程结构化模型。supervisor模块提供了一个监控其他进程的进程。通过supervisor能够构建监控树,用于构造容错应用程序。
上图中,方框提供监督,圆圈是工作者。监控者能够启动、关闭及重启它的子进程。
- 佣程(worker)是进行计算的进程,也就是说,它们进行实际的工作。
- 督程(supervisor)是监视工作者行为的进程。监督者可以重启工作者如果出现了什么问题.
监督树是一种将代码分成监督者和工作者的层次安排,便于设计和编写高度可容错的软件。
8 结语
在此,我们只是浮光掠影进行了解,扩展我们的知识维度。OTP不仅是Erlang技术堆栈的一部分,它也是构建高度可靠、可伸缩软件解决方案的基石,对于那些追求系统稳定性和可维护性的开发者而言,这是一个不容忽视的强大工具箱。当大风吹过之时,一味跟随主流当然不会出错,但是要想迅速脱颖而出,得掏出那些有点难度的但是生产力惊人的工具,从这个意义上来说,Erlang/OTP很可能是击败诸多猿族的秘密武器。