Erlang OTP Application

Erlang OTP Application

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_envget_all_keyget_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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354