美文网首页
Erlang OTP Application

Erlang OTP Application

作者: 坚果jimbowhy | 来源:发表于2020-06-23 12:24 被阅读0次

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

相关文章

网友评论

      本文标题:Erlang OTP Application

      本文链接:https://www.haomeiwen.com/subject/uracfktx.html