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 應(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_serverGeneric server behaviour,實(shí)現(xiàn) C/S 架構(gòu)中的服務(wù)端; -
gen_statemGeneric state machine behaviour,實(shí)現(xiàn)一個(gè)有限狀態(tài)機(jī) FSM - Finite State Machine; -
gen_eventGeneric event handling behavior,實(shí)現(xiàn)事件處理功能; -
supervisorGeneric 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_key 或 get_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。
