檢視原始碼 概述
OTP 設計原則 定義了如何以程序、模組和目錄來組織 Erlang 程式碼。
監管樹
Erlang/OTP 的一個基本概念是監管樹。這是一個基於工作者和監管者概念的程序結構模型。
- 工作者是執行計算和其他實際工作的程序。
- 監管者是監控工作者的程序。如果發生問題,監管者可以重新啟動工作者。
- 監管樹是將程式碼分層排列成監管者和工作者的結構,這使得設計和編寫容錯軟體成為可能。
在下圖中,方塊代表監管者,圓圈代表工作者
---
title: Supervision Tree
---
flowchart
sup1[Type 1 Supervisor] --- sup2[Type 1 Supervisor] --- worker1((worker))
sup1 --- sup1a[Type A Supervisor]
sup1a --- sup2a[Type A Supervisor] --- worker2((worker))
sup1a --- sup3[Type 1 Supervisor]
sup3 --- worker3((worker))
sup3 --- worker4((worker))
行為
在監管樹中,許多程序具有相似的結構並遵循相似的模式。例如,監管者共享相似的結構,唯一的區別在於它們監管的子程序。許多工作者是客戶端-伺服器關係中的伺服器、有限狀態機或事件處理程序。
行為是這些常見模式的形式化。其概念是將程序的程式碼分為通用部分(行為模組)和特定部分(回呼模組)。
行為模組是 Erlang/OTP 的一部分。要實作像是監管者這樣的程序,使用者只需要實作回呼模組,該模組需要匯出預定義的函數集,即回呼函數。
以下範例說明如何將程式碼分為通用部分和特定部分。考慮以下程式碼(以純 Erlang 撰寫)作為一個簡單的伺服器,它會追蹤多個「頻道」。其他程序可以透過呼叫 alloc/0
和 free/1
函數來配置和釋放頻道。
-module(ch1).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0]).
start() ->
spawn(ch1, init, []).
alloc() ->
ch1 ! {self(), alloc},
receive
{ch1, Res} ->
Res
end.
free(Ch) ->
ch1 ! {free, Ch},
ok.
init() ->
register(ch1, self()),
Chs = channels(),
loop(Chs).
loop(Chs) ->
receive
{From, alloc} ->
{Ch, Chs2} = alloc(Chs),
From ! {ch1, Ch},
loop(Chs2);
{free, Ch} ->
Chs2 = free(Ch, Chs),
loop(Chs2)
end.
伺服器的程式碼可以重寫為通用部分 server.erl
-module(server).
-export([start/1]).
-export([call/2, cast/2]).
-export([init/1]).
start(Mod) ->
spawn(server, init, [Mod]).
call(Name, Req) ->
Name ! {call, self(), Req},
receive
{Name, Res} ->
Res
end.
cast(Name, Req) ->
Name ! {cast, Req},
ok.
init(Mod) ->
register(Mod, self()),
State = Mod:init(),
loop(Mod, State).
loop(Mod, State) ->
receive
{call, From, Req} ->
{Res, State2} = Mod:handle_call(Req, State),
From ! {Mod, Res},
loop(Mod, State2);
{cast, Req} ->
State2 = Mod:handle_cast(Req, State),
loop(Mod, State2)
end.
以及回呼模組 ch2.erl
-module(ch2).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0, handle_call/2, handle_cast/2]).
start() ->
server:start(ch2).
alloc() ->
server:call(ch2, alloc).
free(Ch) ->
server:cast(ch2, {free, Ch}).
init() ->
channels().
handle_call(alloc, Chs) ->
alloc(Chs). % => {Ch,Chs2}
handle_cast({free, Ch}, Chs) ->
free(Ch, Chs). % => Chs2
請注意以下幾點
server
中的程式碼可以重複使用以構建許多不同的伺服器。- 伺服器名稱,在此範例中為原子
ch2
,對客戶端函數的使用者是隱藏的。這表示名稱可以變更,而不會影響他們。 - 協定(傳送至伺服器和從伺服器接收的訊息)也會隱藏。這是良好的程式設計實務,並允許您變更協定,而無需變更使用介面函數的程式碼。
server
的功能可以擴展,而無需變更ch2
或任何其他回呼模組。
在上面的 ch1.erl
和 ch2.erl
中,有意省略了 channels/0
、alloc/1
和 free/2
的實作,因為它與範例無關。為了完整起見,下面提供了一種編寫這些函數的方式。這僅是一個範例,實際的實作必須能夠處理諸如配置通道不足等情況。
channels() ->
{_Allocated = [], _Free = lists:seq(1, 100)}.
alloc({Allocated, [H|T] = _Free}) ->
{H, {[H|Allocated], T}}.
free(Ch, {Alloc, Free} = Channels) ->
case lists:member(Ch, Alloc) of
true ->
{lists:delete(Ch, Alloc), [Ch|Free]};
false ->
Channels
end.
未使用行為編寫的程式碼可能更有效率,但效率的提高是以犧牲通用性為代價的。以一致的方式管理系統中的所有應用程式的能力非常重要。
使用行為也讓閱讀和理解其他程式設計師編寫的程式碼變得更容易。即興的程式設計結構雖然可能更有效率,但總是更難以理解。
server
模組對應於簡化的 Erlang/OTP 行為 gen_server
。
標準的 Erlang/OTP 行為有
用於實作客戶端-伺服器關係的伺服器
用於實作狀態機
用於實作事件處理功能
用於在監管樹中實作監管者
編譯器會理解模組屬性 -behaviour(Behaviour)
,並針對遺失的回呼函數發出警告,例如
-module(chs3).
-behaviour(gen_server).
...
3> c(chs3).
./chs3.erl:10: Warning: undefined call-back function handle_call/3
{ok,chs3}
應用程式
Erlang/OTP 附帶了許多組件,每個組件都實作了一些特定的功能。在 Erlang/OTP 術語中,組件稱為應用程式。Erlang/OTP 應用程式的範例包括 Mnesia,它擁有程式設計資料庫服務所需的一切,以及 Debugger,它用於除錯 Erlang 程式。基於 Erlang/OTP 的最小系統由以下兩個應用程式組成
- Kernel - 執行 Erlang 所需的功能
- STDLIB - Erlang 標準函式庫
應用程式概念同時適用於程式結構(程序)和目錄結構(模組)。
最簡單的應用程式沒有任何程序,但由一組功能模組組成。此類應用程式稱為函式庫應用程式。函式庫應用程式的一個範例是 STDLIB。
具有程序的應用程式最容易使用標準行為實作為監管樹。
如何在 應用程式中描述程式設計應用程式的方法。
發行版本
發行版本是由 Erlang/OTP 應用程式的子集和一組使用者特定的應用程式組成的完整系統。
如何在 發行版本中描述程式設計發行版本的方法。
如何在系統原則的 建立和升級目標系統中描述在目標環境中安裝發行版本的方法。
發行版本處理
發行版本處理是在(可能)運行的系統中,在發行版本的不同版本之間進行升級和降級。如何在 發行版本處理中描述執行此操作的方法。