檢視原始碼 概述

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/0free/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.erlch2.erl 中,有意省略了 channels/0alloc/1free/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 應用程式的子集和一組使用者特定的應用程式組成的完整系統。

如何在 發行版本中描述程式設計發行版本的方法。

如何在系統原則的 建立和升級目標系統中描述在目標環境中安裝發行版本的方法。

發行版本處理

發行版本處理是在(可能)運行的系統中,在發行版本的不同版本之間進行升級和降級。如何在 發行版本處理中描述執行此操作的方法。