Erlang OTP Application
- Application
- application module
- Application resource file - .app
- Boot script file - .script
- Release resource file - .rel
- Release upgrade file - .relup
- Application upgrade file - .appup
- Creating and Upgrading a Target System
- Upgrade when Erlang/OTP has Changed
- Appup Cookbook
- Release Handling
- Release Structure
-
Building applications with OTP
Erlang 应用程序就是一组相关代码和进程,使用 OTP 框架的程序就是 Erlang/OTP 应用程序。
启动 Erlang shell 后,默认至少运行了以下两个程序:
- kernel Application
- STDLIB Application
不要和将这里运行的 STDLIB 程序和标准库混乱,这是两个不同的东西,一般来说,还有一个 sasl 程序也是经常需要用到的,因为这三个应用程序是 Erlang 系统中运行的最基本程序。
Kernel 应用程序拥有运行基础代码以运行 Erlang runtime system,包括文件服务、代码服务等等,它是第一运行和程序。它也是强制运行的,因为基于 Erlang/OTP 最小系统就包括 Kernel 和 STDLIB 两个程序。
STDLIB 应用程序不提供服务,Kernel 应用程序功能如下:
- 应用程序的启动、停止,supervision 监督, 配置, 分布应用程序;
- 代码加载 Code loading;
- 日志记录 Logging;
- 全局进程名称服务 Global name service;
- 监督系统 Supervision of Erlang/OTP;
- 套接通信 Communication with sockets;
- 操作系统接口 Operating system interface;
注意 Erlang/OTP 的 SASL 应用与 RFC 4422 文档中的 Simple Authentication and Security Layer 没有任何关系。SASL 全称 System Architecture Support Libraries,为 Erlang/OTP 应用程序架构提供了以下机制支持,主要用于应用程序的发布打包升级等:
- alarm_handler 警告事件处理机制;
- release_handler 程序发布机制;
- systools 程序发布打包生成工具;
Erlang/OTP 工程的基本框架,即 Supervision Tree 架构:
- 项目可以包含很多个
Application
,它包含了本应用的所有代码,可以随时加载和关闭; - Application 一般会包含一个顶层
Supervisor
进程用来监控Worker
,这使得设计和编程容错软件成为可能; - 顶层 Supervisor 下面管理了许多 sub Supervisor 和 Worker 进程。
- 业务逻辑都在 Worker 里面,Supervisor 里可以定制重启策略,如果返现某个 Worker 挂掉了,可以按照既定的策略重启它。
Supervisor 负责启动,停止和监视其子进程,基本思想是通过在必要时重新启动它们来保持子进程的活动。
在 Erlang/OTP 架构中,一切进程都是轻量级的,都可以被监控 monitor,有 Supervisor 专门做监控。你可以方便的用一个 Supervisor 进程去管理子进程,它会根据你设定的策略,来处理意外挂掉的子进程。这种情况的问题的是,错误处理稍微做不好就会挂,Restart Strategy 重启策略有:
- one_for_one:只重启挂掉的子进程
- one_for_all:有一个子进程挂了,重启所有子进程
- rest_for_one:在该挂掉的子进程 创建时间之后创建的子进程都会重启。
在监督树中,许多流程具有相似的结构,它们遵循类似的模式,即抽象为 Behaviour 模型。Supervisor 的结构相似,他们之间唯一的区别是他们监督哪个子进程。许多 Worker 都是 C/S 服务器对客户端关系模式中的服务器角色,Worker 对应各种 Behaviour,包括有限状态机器 gen_statem、错误事件记录器 gen_event 等事件处理程序,还有 gen_server 通用服务器行为。
总结起来,Erlang/OTP 系统就是三大基础应用程序,四大 Behaviour 中,除 Supervisor 外,都在监督树充当 Worker 角色:
-
gen_server
Generic server behaviour,实现 C/S 架构中的服务端; -
gen_statem
Generic state machine behaviour,实现一个有限状态机 FSM - Finite State Machine; -
gen_event
Generic event handling behavior,实现事件处理功能; -
supervisor
Generic supervisor behavior,实现监督者,它以监督树的方式存在;
推荐的开发阶段使用的目录结构:
─ ${application}
├── doc
│ ├── internal
│ ├── examples
│ └── src
├── include
├── priv
├── src
│ └── ${application}.app.src
└── test
-
src
必要的,存放源代码,内部头文件等,子目录可以作为命名空间组织,但不应该有二级子目录! -
priv
- Optional,存放程序指定文件; -
include
- Optional,存放公开头文件等; -
doc
- Recommended,源代码文档,应该存放在子目录下; -
doc/internal
- Recommended,存放内部实现代码细节文档; -
doc/examples
- Recommended,存放公开的示例源代码; -
doc/src
- Recommended,归档原文件,如 Markdown, AsciiDoc 或 XML-files; -
test
- Recommended,保存用于测试的文档,如测试脚本等;
其它目录可以根据需要添加,比如 c_src 存放 C 代码,java_src 存放 Java 代码,go_src 存放 Go 代码。
发布后,推荐使用的应用程序目录结构:
─ ${application}-${version}
├── bin
├── doc
│ ├── html
│ ├── man[1-9]
│ ├── pdf
│ ├── internal
│ └── examples
├── ebin
│ └── ${application}.app
├── include
├── priv
│ ├── lib
│ └── bin
└── src
-
src
- Optional,包含公开的代码及内部头文件; -
ebin
- 必要的,存放编译后的 Erlang 字节码文件.beam
,还有.app
必需保存在此; -
priv
- Optional,存放程序指定文件,可以使用code:priv_dir/1
函数访问; -
priv/lib
- 推荐的,共享对象文件,如 NIFs 或 linked-in-drivers 应该存放在此; -
priv/bin
- 推荐的,可以执行文件,如 port-programs 应该存放在此; -
include
- Optional,存放公共头文件等; -
bin
- Optional,存放可执行文件,如 escripts 或 shell-scripts; -
doc
- Optional,存放发布文档等资源; -
doc/man1
- 推荐的,存放 Application executables 手册; -
doc/man3
- 推荐的,存放 module APIs 手册; -
doc/man6
- 推荐的,存放 Application overview 手册; -
doc/html
- Optional,存放整个应用的 HTML 页面; -
doc/pdf
- Optional,存入整个应用的 PDF 文档;
在经典 Erlang/OTP 程序框架中,Supervision Tree 框架应用程序实现了各种上回调模块,包括 Application Callback Module,它提供了两个函数来启动或终止应用:
- start(StartType, StartArgs) -> {ok, Pid} or {ok, Pid, State}
- stop(State)
参数解析:
-
start
函数在顶级 Supervisor 启动应用时进行回调,需要它返回顶级 Supervisor 的 pid 和选项,还有状态数据 State,默认是空列表 []; -
StartArgs
启动参数,可以由应用程序资源文件.app
定义的mod
设置; -
stop/1
在应用程序停止后或做清理时调用,真实的停止是 Supervision Tree 关闭时。
在 Erlang 中,程序可以包含其它任意个程序,Included Applications,有独立的目录结构,子程序同时只能被一个程序拥有,通常通过 Supervision Tree 启动。而只包含子程序的顶级程序,叫做主程序 Primary Application。它们可以在 .app
文件中配置,子程序在启动时的同步可以使用 start_phases
设置,而 mod
必需设置为 {application_starter,[Module,StartArgs]}
,StartArgs 会在子程序启动时传入 Module:start/2
函数:
{application, prim_app,
[{description, "Tree application"},
{vsn, "1"},
{modules, [prim_app_cb, prim_app_sup, prim_app_server]},
{registered, [prim_app_server]},
{included_applications, [incl_app]},
{start_phases, [{init,[]}, {go,[]}]},
{applications, [kernel, stdlib, sasl]},
{mod, {application_starter,[prim_app_cb,[]]}},
{env, [{file, "/usr/local/log"}]}
]}.
{application, incl_app,
[{description, "Included application"},
{vsn, "1"},
{modules, [incl_app_cb, incl_app_sup, incl_app_server]},
{registered, []},
{start_phases, [{go,[]}]},
{applications, [kernel, stdlib, sasl]},
{mod, {incl_app_cb,[]}}
]}.
Erlang 运行时系统启动后,需要一些进程来与应用程序进行交互,它们注册为程序控制器进程
application_controller,就像是 Kernel application 核心进程的一部分。应用程序可以进行四种基本操作,loaded
, unloaded
, started
, stopped
。
示例,Supervision Tree 框架应用程序回调模块:
ch_sup.erl 模块:
-module(ch_sup).
-behaviour(supervisor).
-compile([export_all,nowarn_export_all]).
% -export([start_link/0]).
% -export([init/1]).
start_link() ->
supervisor:start_link(ch_sup, []).
init(_Args) ->
SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
ChildSpecs = [#{id => ch3,
start => {ch3, start_link, []},
restart => permanent,
shutdown => brutal_kill,
type => worker,
modules => [cg3]}],
{ok, {SupFlags, ChildSpecs}}.
ch_app.erl 模块:
-module(ch_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_Type, _Args) ->
ch_sup:start_link().
stop(_State) ->
ok.
ch_app.app 应用程序资源文件:
{application, ch_app,
[{description, "Channel allocator"},
{vsn, "1"},
{modules, [ch_app, ch_sup, ch3]},
{registered, [ch3]},
{applications, [kernel, stdlib, sasl]},
{mod, {ch_app,[]}},
{env, [{file, "/usr/local/log"}]}
]}.
加载应用程序 application:load 需要用到 .app
文件,文件名必需和指定的 atom 程序名相同,每个选项都是 {Key,Value}
元组键值对。其中 mod
主键指定的值就是应用程序启动时的参数,这里指定的是 ch_app 和 []。这里指定的 env
这些配置可以使用 application 模块的 get_env
、get_all_key
或 get_key
方法获取相应数据。
在发布应用程序时,可以使用 Erlang/OTP 提供的打包工具 systools
生成应用程序资源文件。它生成的文件包含以下这些主键 description
, vsn
, modules
, registered
, applications
。
其中 applications 主键设置了应用程序的依赖项,kernel、stdlib、sasl 是程序的基础依赖,默认前两者是加载的。注意,如果依赖模块没有运行,是不能够运行 Application 的:
1> application:start(ch_app).
{error,{not_started,sasl}}
2> application:start(sasl).
ok
一个程序停止后,卸载后,或者根本没有开始,它就会从 Erlang 内部的应用控制器数据库中移除:
1> application:load(ch_app).
ok
2> application:loaded_applications().
[{kernel,"ERTS CXC 138 10","2.8.1.3"},
{stdlib,"ERTS CXC 138 10","1.11.4.3"},
{ch_app,"Channel allocator","1"}]
3> application:start(ch_app).
ok
4> application:get_env(ch_app, file).
{ok,"/usr/local/log"}
5> application:unload(ch_app).
ok
6> application:loaded_applications().
[{kernel,"ERTS CXC 138 10","2.8.1.3"},
{stdlib,"ERTS CXC 138 10","1.11.4.3"}]
7> application:which_applications().
[{stdlib,"ERTS CXC 138 10","3.9"},
{kernel,"ERTS CXC 138 10","6.4"}]
9> application:get_all_key(ch_app).
{ok,[{description,"Channel allocator"},
{id,[]},
{vsn,"1"},
{modules,[ch_app,ch_sup,ch3]},
{maxP,infinity},
{maxT,infinity},
{registered,[ch3]},
{included_applications,[]},
{applications,[kernel,stdlib,sasl]},
{env,[{file,"/usr/local/log"}]},
{mod,{ch_app,[]}},
{start_phases,undefined}]}
可以指定配置运行程序,假设内容 test.config
配置内容 [{ch_app, [{file, "testlog"}]}].
,注意有句点:
% erl -config test
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}
如果使用了 Release Handling 还可以配置 sys.config,包括 .app
文件的配置所有配置都可以通过命令行指定:
% erl -ApplName Par1 Val1 ... ParN ValN
Example:
% erl -ch_app file '"testlog"'
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}
应用程序启动时,可以指定启动类型,永久方式
permanent 或临时方式
transient:
- application:start(Application, Type)
- application:start(Application)
- application:start(Application, temporary)
默认是临时方式,永久方式启动的应用程序终止意味着运行时系统也终止。
临时应用程序可以是正常终止 normal
,也可以是其它反常终止 abnormally
,这时其它程序和运行时系统也终止,并产生一个 normal
以外的 Reason。如果是终结 terminates
,那么其它应用程序不会终止。
应用程序总是可以通过 application:stop/1
函数结束,不管什么运行模式,并且不影响其它应用程序。
临时应用程序在实践中少见,因为,Supervision Tree 结束时产的 Reason 是 shutdown
而不是 normal
。