总览 | 1. Overview

1 概览

所述 OTP 设计原则定义如何构建的 Erlang 代码中的过程,模块,和目录条款。

1.1 监督树(Supervision Trees)

Erlang / OTP 中的基本概念是监督树。这是一个基于工人主管理念的流程构建模型:

  • 工人是执行计算的过程,也就是说,他们做实际的工作。

  • 主管是监督工作者行为的过程。如果出现问题,主管可以重新启动员工。

  • 监督树是代码到监督员和工作人员的分级安排,这使得设计和编程容错软件成为可能。

在下图中,方框代表主管,圆圈代表工作人员:

1.2 行为(Behaviours)

在监督树中,许多流程具有相似的结构,它们遵循类似的模式。例如,主管的结构相似。他们之间唯一的区别是他们监督哪个子进程。许多工作者都是服务器 - 客户端关系中的服务器,有限状态机器或错误记录器等事件处理程序。

行为是这些常见模式的形式化。这个想法是在通用部分(行为模块)和特定部分(回调模块)中划分进程的代码。

行为模块是 Erlang / OTP 的一部分。为了实现诸如监督员之类的过程,用户只需实现回调模块,该回调模块将导出预先定义的一组功能,即回调功能

以下示例说明如何将代码划分为通用部分和特定部分。考虑下面的代码(用简单的 Erlang 编写)为一个简单的服务器,它跟踪了一些“通道”。其它进程可通过分别调用函数alloc/0free/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.erlch2.erl以上,实施channels/0alloc/1free/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