Erlang OTP Application

Erlang OTP Application

Erlang 應(yīng)用程序就是一組相關(guān)代碼和進(jìn)程,使用 OTP 框架的程序就是 Erlang/OTP 應(yīng)用程序。

啟動 Erlang shell 后,默認(rèn)至少運(yùn)行了以下兩個(gè)程序:

  • kernel Application
  • STDLIB Application

不要和將這里運(yùn)行的 STDLIB 程序和標(biāo)準(zhǔn)庫混亂,這是兩個(gè)不同的東西,一般來說,還有一個(gè) sasl 程序也是經(jīng)常需要用到的,因?yàn)檫@三個(gè)應(yīng)用程序是 Erlang 系統(tǒng)中運(yùn)行的最基本程序。

Kernel 應(yīng)用程序擁有運(yùn)行基礎(chǔ)代碼以運(yùn)行 Erlang runtime system,包括文件服務(wù)、代碼服務(wù)等等,它是第一運(yùn)行和程序。它也是強(qiáng)制運(yùn)行的,因?yàn)榛?Erlang/OTP 最小系統(tǒng)就包括 Kernel 和 STDLIB 兩個(gè)程序。

STDLIB 應(yīng)用程序不提供服務(wù),Kernel 應(yīng)用程序功能如下:

  • 應(yīng)用程序的啟動、停止,supervision 監(jiān)督, 配置, 分布應(yīng)用程序;
  • 代碼加載 Code loading;
  • 日志記錄 Logging;
  • 全局進(jìn)程名稱服務(wù) Global name service;
  • 監(jiān)督系統(tǒng) Supervision of Erlang/OTP;
  • 套接通信 Communication with sockets;
  • 操作系統(tǒng)接口 Operating system interface;

注意 Erlang/OTP 的 SASL 應(yīng)用與 RFC 4422 文檔中的 Simple Authentication and Security Layer 沒有任何關(guān)系。SASL 全稱 System Architecture Support Libraries,為 Erlang/OTP 應(yīng)用程序架構(gòu)提供了以下機(jī)制支持,主要用于應(yīng)用程序的發(fā)布打包升級等:

  • alarm_handler 警告事件處理機(jī)制;
  • release_handler 程序發(fā)布機(jī)制;
  • systools 程序發(fā)布打包生成工具;

Erlang/OTP 工程的基本框架,即 Supervision Tree 架構(gòu):

  • 項(xiàng)目可以包含很多個(gè) Application,它包含了本應(yīng)用的所有代碼,可以隨時(shí)加載和關(guān)閉;
  • Application 一般會包含一個(gè)頂層 Supervisor 進(jìn)程用來監(jiān)控 Worker,這使得設(shè)計(jì)和編程容錯軟件成為可能;
  • 頂層 Supervisor 下面管理了許多 sub Supervisor 和 Worker 進(jìn)程。
  • 業(yè)務(wù)邏輯都在 Worker 里面,Supervisor 里可以定制重啟策略,如果返現(xiàn)某個(gè) Worker 掛掉了,可以按照既定的策略重啟它。

Supervisor 負(fù)責(zé)啟動,停止和監(jiān)視其子進(jìn)程,基本思想是通過在必要時(shí)重新啟動它們來保持子進(jìn)程的活動。

在 Erlang/OTP 架構(gòu)中,一切進(jìn)程都是輕量級的,都可以被監(jiān)控 monitor,有 Supervisor 專門做監(jiān)控。你可以方便的用一個(gè) Supervisor 進(jìn)程去管理子進(jìn)程,它會根據(jù)你設(shè)定的策略,來處理意外掛掉的子進(jìn)程。這種情況的問題的是,錯誤處理稍微做不好就會掛,Restart Strategy 重啟策略有:

  • one_for_one:只重啟掛掉的子進(jìn)程
  • one_for_all:有一個(gè)子進(jìn)程掛了,重啟所有子進(jìn)程
  • rest_for_one:在該掛掉的子進(jìn)程 創(chuàng)建時(shí)間之后創(chuàng)建的子進(jìn)程都會重啟。

在監(jiān)督樹中,許多流程具有相似的結(jié)構(gòu),它們遵循類似的模式,即抽象為 Behaviour 模型。Supervisor 的結(jié)構(gòu)相似,他們之間唯一的區(qū)別是他們監(jiān)督哪個(gè)子進(jìn)程。許多 Worker 都是 C/S 服務(wù)器對客戶端關(guān)系模式中的服務(wù)器角色,Worker 對應(yīng)各種 Behaviour,包括有限狀態(tài)機(jī)器 gen_statem、錯誤事件記錄器 gen_event 等事件處理程序,還有 gen_server 通用服務(wù)器行為。

總結(jié)起來,Erlang/OTP 系統(tǒng)就是三大基礎(chǔ)應(yīng)用程序,四大 Behaviour 中,除 Supervisor 外,都在監(jiān)督樹充當(dāng) Worker 角色:

  • gen_server Generic server behaviour,實(shí)現(xiàn) C/S 架構(gòu)中的服務(wù)端;
  • gen_statem Generic state machine behaviour,實(shí)現(xiàn)一個(gè)有限狀態(tài)機(jī) FSM - Finite State Machine;
  • gen_event Generic event handling behavior,實(shí)現(xiàn)事件處理功能;
  • supervisor Generic supervisor behavior,實(shí)現(xiàn)監(jiān)督者,它以監(jiān)督樹的方式存在;

推薦的開發(fā)階段使用的目錄結(jié)構(gòu):

─ ${application}
  ├── doc
  │   ├── internal
  │   ├── examples
  │   └── src
  ├── include
  ├── priv 
  ├── src 
  │   └── ${application}.app.src
  └── test
  • src 必要的,存放源代碼,內(nèi)部頭文件等,子目錄可以作為命名空間組織,但不應(yīng)該有二級子目錄!
  • priv - Optional,存放程序指定文件;
  • include - Optional,存放公開頭文件等;
  • doc - Recommended,源代碼文檔,應(yīng)該存放在子目錄下;
  • doc/internal - Recommended,存放內(nèi)部實(shí)現(xiàn)代碼細(xì)節(jié)文檔;
  • doc/examples - Recommended,存放公開的示例源代碼;
  • doc/src - Recommended,歸檔原文件,如 Markdown, AsciiDoc 或 XML-files;
  • test - Recommended,保存用于測試的文檔,如測試腳本等;

其它目錄可以根據(jù)需要添加,比如 c_src 存放 C 代碼,java_src 存放 Java 代碼,go_src 存放 Go 代碼。

發(fā)布后,推薦使用的應(yīng)用程序目錄結(jié)構(gòu):

─ ${application}-${version}
  ├── bin
  ├── doc
  │   ├── html
  │   ├── man[1-9]
  │   ├── pdf
  │   ├── internal
  │   └── examples
  ├── ebin
  │   └── ${application}.app
  ├── include
  ├── priv
  │   ├── lib
  │   └── bin
  └── src
  • src - Optional,包含公開的代碼及內(nèi)部頭文件;
  • ebin - 必要的,存放編譯后的 Erlang 字節(jié)碼文件 .beam,還有 .app 必需保存在此;
  • priv - Optional,存放程序指定文件,可以使用 code:priv_dir/1 函數(shù)訪問;
  • priv/lib - 推薦的,共享對象文件,如 NIFs 或 linked-in-drivers 應(yīng)該存放在此;
  • priv/bin - 推薦的,可以執(zhí)行文件,如 port-programs 應(yīng)該存放在此;
  • include - Optional,存放公共頭文件等;
  • bin - Optional,存放可執(zhí)行文件,如 escripts 或 shell-scripts;
  • doc - Optional,存放發(fā)布文檔等資源;
  • doc/man1 - 推薦的,存放 Application executables 手冊;
  • doc/man3 - 推薦的,存放 module APIs 手冊;
  • doc/man6 - 推薦的,存放 Application overview 手冊;
  • doc/html - Optional,存放整個(gè)應(yīng)用的 HTML 頁面;
  • doc/pdf - Optional,存入整個(gè)應(yīng)用的 PDF 文檔;

在經(jīng)典 Erlang/OTP 程序框架中,Supervision Tree 框架應(yīng)用程序?qū)崿F(xiàn)了各種上回調(diào)模塊,包括 Application Callback Module,它提供了兩個(gè)函數(shù)來啟動或終止應(yīng)用:

  • start(StartType, StartArgs) -> {ok, Pid} or {ok, Pid, State}
  • stop(State)

參數(shù)解析:

  • start 函數(shù)在頂級 Supervisor 啟動應(yīng)用時(shí)進(jìn)行回調(diào),需要它返回頂級 Supervisor 的 pid 和選項(xiàng),還有狀態(tài)數(shù)據(jù) State,默認(rèn)是空列表 [];
  • StartArgs 啟動參數(shù),可以由應(yīng)用程序資源文件 .app 定義的 mod 設(shè)置;
  • stop/1 在應(yīng)用程序停止后或做清理時(shí)調(diào)用,真實(shí)的停止是 Supervision Tree 關(guān)閉時(shí)。

在 Erlang 中,程序可以包含其它任意個(gè)程序,Included Applications,有獨(dú)立的目錄結(jié)構(gòu),子程序同時(shí)只能被一個(gè)程序擁有,通常通過 Supervision Tree 啟動。而只包含子程序的頂級程序,叫做主程序 Primary Application。它們可以在 .app 文件中配置,子程序在啟動時(shí)的同步可以使用 start_phases 設(shè)置,而 mod 必需設(shè)置為 {application_starter,[Module,StartArgs]},StartArgs 會在子程序啟動時(shí)傳入 Module:start/2 函數(shù):

{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 運(yùn)行時(shí)系統(tǒng)啟動后,需要一些進(jìn)程來與應(yīng)用程序進(jìn)行交互,它們注冊為程序控制器進(jìn)程 application_controller,就像是 Kernel application 核心進(jìn)程的一部分。應(yīng)用程序可以進(jìn)行四種基本操作,loaded, unloaded, started, stopped。

示例,Supervision Tree 框架應(yīng)用程序回調(diào)模塊:

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 應(yīng)用程序資源文件:

{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"}]}
 ]}.

加載應(yīng)用程序 application:load 需要用到 .app 文件,文件名必需和指定的 atom 程序名相同,每個(gè)選項(xiàng)都是 {Key,Value} 元組鍵值對。其中 mod 主鍵指定的值就是應(yīng)用程序啟動時(shí)的參數(shù),這里指定的是 ch_app 和 []。這里指定的 env 這些配置可以使用 application 模塊的 get_env、get_all_keyget_key 方法獲取相應(yīng)數(shù)據(jù)。

在發(fā)布應(yīng)用程序時(shí),可以使用 Erlang/OTP 提供的打包工具 systools 生成應(yīng)用程序資源文件。它生成的文件包含以下這些主鍵 description, vsn, modules, registered, applications。

其中 applications 主鍵設(shè)置了應(yīng)用程序的依賴項(xiàng),kernel、stdlib、sasl 是程序的基礎(chǔ)依賴,默認(rèn)前兩者是加載的。注意,如果依賴模塊沒有運(yùn)行,是不能夠運(yùn)行 Application 的:

1> application:start(ch_app).
{error,{not_started,sasl}}
2> application:start(sasl).
ok

一個(gè)程序停止后,卸載后,或者根本沒有開始,它就會從 Erlang 內(nèi)部的應(yīng)用控制器數(shù)據(jù)庫中移除:

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}]}

可以指定配置運(yùn)行程序,假設(shè)內(nèi)容 test.config 配置內(nèi)容 [{ch_app, [{file, "testlog"}]}].,注意有句點(diǎn):

% 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"}

應(yīng)用程序啟動時(shí),可以指定啟動類型,永久方式 permanent 或臨時(shí)方式 transient:

  • application:start(Application, Type)
  • application:start(Application)
  • application:start(Application, temporary)

默認(rèn)是臨時(shí)方式,永久方式啟動的應(yīng)用程序終止意味著運(yùn)行時(shí)系統(tǒng)也終止。

臨時(shí)應(yīng)用程序可以是正常終止 normal,也可以是其它反常終止 abnormally,這時(shí)其它程序和運(yùn)行時(shí)系統(tǒng)也終止,并產(chǎn)生一個(gè) normal 以外的 Reason。如果是終結(jié) terminates,那么其它應(yīng)用程序不會終止。

應(yīng)用程序總是可以通過 application:stop/1 函數(shù)結(jié)束,不管什么運(yùn)行模式,并且不影響其它應(yīng)用程序。

臨時(shí)應(yīng)用程序在實(shí)踐中少見,因?yàn)椋琒upervision Tree 結(jié)束時(shí)產(chǎn)的 Reason 是 shutdown 而不是 normal。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容