总览 | 1. Overview
1 概览
所述 OTP 设计原则
定义如何构建的 Erlang 代码中的过程,模块,和目录条款。
1.1 监督树(Supervision Trees)
Erlang / OTP 中的基本概念是监督树
。这是一个基于工人
和主管
理念的流程构建模型:
- 工人是执行计算的过程,也就是说,他们做实际的工作。
- 主管是监督工作者行为的过程。如果出现问题,主管可以重新启动员工。
- 监督树是代码到监督员和工作人员的分级安排,这使得设计和编程容错软件成为可能。
在下图中,方框代表主管,圆圈代表工作人员:
1.2 行为(Behaviours)
在监督树中,许多流程具有相似的结构,它们遵循类似的模式。例如,主管的结构相似。他们之间唯一的区别是他们监督哪个子进程。许多工作者都是服务器 - 客户端关系中的服务器,有限状态机器或错误记录器等事件处理程序。
行为
是这些常见模式的形式化。这个想法是在通用部分(行为
模块)和特定部分(回调模块
)中划分进程的代码。
行为模块是 Erlang / OTP 的一部分。为了实现诸如监督员之类的过程,用户只需实现回调模块,该回调模块将导出预先定义的一组功能,即回调功能
。
以下示例说明如何将代码划分为通用部分和特定部分。考虑下面的代码(用简单的 Erlang 编写)为一个简单的服务器,它跟踪了一些“通道”。其它进程可通过分别调用函数alloc/0
和free/1
来分配和释放通道。
-module(ch1).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0]).
start() ->
spawn(ch1, init, []).
alloc() ->
ch1 ! {self(), alloc},
receive
{ch1, Res} ->
Res
end.
free(Ch) ->
ch1 ! {free, Ch},
ok.
init() ->
register(ch1, self()),
Chs = channels(),
loop(Chs).
loop(Chs) ->
receive
{From, alloc} ->
{Ch, Chs2} = alloc(Chs),
From ! {ch1, Ch},
loop(Chs2
{free, Ch} ->
Chs2 = free(Ch, Chs),
loop(Chs2)
end.
服务器的代码可以重写为通用部分server.erl
:
-module(server).
-export([start/1]).
-export([call/2, cast/2]).
-export([init/1]).
start(Mod) ->
spawn(server, init, [Mod]).
call(Name, Req) ->
Name ! {call, self(), Req},
receive
{Name, Res} ->
Res
end.
cast(Name, Req) ->
Name ! {cast, Req},
ok.
init(Mod) ->
register(Mod, self()),
State = Mod:init(),
loop(Mod, State).
loop(Mod, State) ->
receive
{call, From, Req} ->
{Res, State2} = Mod:handle_call(Req, State),
From ! {Mod, Res},
loop(Mod, State2
{cast, Req} ->
State2 = Mod:handle_cast(Req, State),
loop(Mod, State2)
end.
和一个回调模块ch2.erl
:
-module(ch2).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0, handle_call/2, handle_cast/2]).
start() ->
server:start(ch2).
alloc() ->
server:call(ch2, alloc).
free(Ch) ->
server:cast(ch2, {free, Ch}).
init() ->
channels().
handle_call(alloc, Chs) ->
alloc(Chs). % => {Ch,Chs2}
handle_cast{free, Ch}, Chs) ->
free(Ch, Chs). % => Chs2
注意以下几点:
- 代码
server
可以被重用来构建许多不同的服务器。
- 服务器名称(在本例中为原子)
ch2
对客户端功能的用户是隐藏的。这意味着名称可以更改而不会影响它们。
- 协议(发送到服务器和从服务器接收的消息)也被隐藏。这是一个很好的编程习惯,并且允许更改协议,而无需使用接口函数更改代码。
- 可以扩展
server
功能,而无需更改ch2
或任何其他回调模块。
在ch1.erl
与ch2.erl
以上,实施channels/0
,alloc/1
和free/2
被有意忽略,因为它是不相关的例子。为了完整性,下面给出了编写这些函数的一种方法。这只是一个例子,一个现实的实现必须能够处理诸如用尽分配通道等情况。
channels() ->
{_Allocated = [], _Free = lists:seq(1,100)}.
alloc{Allocated, [H|T] = _Free}) ->
{H, {[H|Allocated], T}}.
free(Ch, {Alloc, Free} = Channels) ->
case lists:member(Ch, Alloc) of
true ->
{lists:delete(Ch, Alloc), [Ch|Free]};
false ->
Channels
end.
不使用行为编写的代码可以更高效,但提高效率是以牺牲一般性为代价的。以一致的方式管理系统中所有应用程序的能力非常重要。
使用行为还可以更轻松地阅读和理解其他程序员编写的代码。简化的编程结构虽然可能更高效,但总是更难以理解。
server
模块与 Erlang / OTP 行为相对应,大大简化gen_server
。
标准的 Erlang / OTP 行为是:
gen_server
用于实现客户端 - 服务器关系的服务器
gen_statem
用于实现状态机
gen_event
用于实现事件处理功能
supervisor
在监督树中实施监督员
编译器理解模块属性-behaviour(Behaviour)
并发出关于缺少回调函数的警告,例如:
-module(chs3).
-behaviour(gen_server).
...
3> c(chs3).
./chs3.erl:10: Warning: undefined call-back function handle_call/3
{ok,chs3}
1.3 应用(Applications)
Erlang / OTP 带有许多组件,每个组件都实现了一些特定的功能。在Erlang / OTP 术语中这些组件被称为应用程序(Application)
。Erlang / OTP 应用程序的例子有 Mnesia,它具有编程数据库服务所需的所有功能,以及 Debugger,用于调试 Erlang 程序。基于 Erlang / OTP 的最小系统由以下两个应用程序组成:
- 内核 - 运行 Erlang 所必需的功能
- STDLIB - Erlang 标准库
应用程序概念同时适用于程序结构(进程)和目录结构(模块)。
最简单的应用程序没有任何进程,但由一组功能模块组成。这样的应用程序被称为库应用
。库应用
程序的一个示例是 STDLIB。
具有流程的应用程序最容易实现为使用标准行为的监督树。
如何编程应用程序在中描述Applications
。
1.4 发布(Releases)
发行版本
是由 Erlang/ OTP 应用程序的子集和一组用户特定的应用程序所开发的完整的系统。
如何编程发布如下所述Releases
。
有关如何在目标环境中安装版本的信息,请参见第2章系统原则中有关目标系统的部分。
1.5 版本处理(Release Handling)
发布处理
是在(可能)正在运行的系统中升级和降级发行版的不同版本。关于如何做到这一点请参见Release Handling
。