Table Of Contents

Previous topic

11. プロファイリング

Next topic

2. gen_serverビヘイビア

This Page

1. 概要

OTPデザイン原則集は、プロセス、モジュール、ディレクトリといった用語セットを使って、実用的なErlangコードを組み立てるための原則集です。

1.1. 監視ツリー

Erlang/OTPの基本的なコンセプトが、 監視ツリー (supervision tree)です。これは、 ワーカースーパバイザ といったアイディアを元にした、プロセスの構造化モデルです。

  • ワーカーは、計算を行うプロセスです。これは、その名のとおり実際に働きます。
  • スーパバイザはワーカーの振る舞いをモニターするプロセスです。スーパバイザは何か問題があった場合に、ワーカーを再起動することがあります。
  • は、スーパバイザとワーカーが階層状に配置されたコードで、フォールトトレラントなソフトウェアの設計とプログラム開発を行えるようにします。
監視ツリー

監視ツリー

上記の図は、四角い箱がスーパーバイザーを、円がワーカーを示しています。

1.2. ビヘイビア

監視ツリーでは、多くのプロセスが同じようなパターンに従った、似た構造をしています。例えば、スーパバイザははどれも似たような構造をしています。それらの違いはスーパバイザが監視する子供のプロセスだけです。そのため、多くのワーカーがサーバ=クライアントの関係におけるサーバであって、有限状態機械(FSM)を構成していて、エラーロガーのイベントハンドラをしています。

ビヘイビア というのは、これらの共通のパターンを形式化したものです。ビヘイビアの考え方は、プロセスに関する汎用的なコード(ビヘイビアモジュール)と、特定の部分(コールバックモジュール)に分割するというものです。

ビヘイビアモジュールはErlang/OTPの一部です。スーパバイザのようなプロセスを実装するためには、ユーザは事前定義の関数と コールバック関数 をエクスポートしている、コールバックのモジュールだけを定義すればよくなります。

どのようにして、汎用的なコードと特化したコードに分割されるのか、というサンプルを紹介します。プレーンなErlangで記述された、シンプルなサーバを実装した以下のコードについて見てみます。このサーバーは”channels”という数値を監視し続けます。他のプロセスは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

以上から、以下のような気づきが得られます:

  • サーバコードは多くの異なるサーバの構築の際に再利用することができます
  • サーバ名は、このサンプルではアトムのch2になります。これはクライアント関数のユーザから隠されます。これは、ユーザに影響を与えずに名前を変更することができるということをあらわしています。
  • サーバへのメッセージ送信と、サーバからの受信のプロトコルをうまく隠蔽されます。これは、インタフェース関数を利用しているコードを変更することなく、プロトコルの変更が可能になるため、よいプログラミングのプラクティスといえます。
  • ch2や他のコールバックモジュールを変更しないで、サーバの機能を拡張することができます。

上記の ch1.erlch2.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.

ビヘイビアを利用せずに書かれたコードは、処理速度の面では効率的かもしれませんが、効率を改善するために一般性を犠牲にすることになります。一貫した方法で、システム内のすべてのアプリケーションを管理できるようにすることは非常に大切です。

ビヘイビアを利用すると、他のプログラマが書いたコードを読んで理解するのが容易になります。その場限りのアドホックなプログラム構造では、効率が良い場合もあるかもしれませんが、たいていの場合は理解が難しくなります。

サーバのモジュールは、Erlang/OTPのビヘイビアのgen_serverに対応します。gen_serverを使うことで大幅に簡略化されます。

標準的なErlang/OTPのビヘイビアには以下のものがあります:

gen_serverビヘイビア

クライアント=サーバの関係における、サーバを実装しています

gen_fsmビヘイビア

有限状態機械を実装しています

gen_eventビヘイビア

イベントハンドリングの機能を実装しています

スーパバイザ・ビヘイビア

監視ツリーのスーパバイザを実装しています

コンパイラはモジュール属性の -behaviour(Behaviour) という行を理解します。もしもコールバック関数が足りない場合には、以下のように警告を出します:

-module(chs3).
-behaviour(gen_server).
...

3> c(chs3).
./chs3.erl:10: Warning: undefined call-back function handle_call/3
{ok,chs3}

1.3. アプリケーション

Erlang/OTPはいくつものコンポーネントを伴います。それぞれのコンポーネントは、特定の機能を実装しています。Erlang/OTPの用語では、コンポーネントを アプリケーション と呼びます。Erlang/OTPアプリケーションのサンプルはMnesiaです。これはデータベースサービスをプログラムするのに必要なすべての機能を持っています。また、Debuggerもアプリケーションです。これはErlangのプログラムのデバッグに使用されます。最小のErlang/OTPベースのシステムは、KernelアプリケーションとSTDLIBアプリケーションを含みます。

アプリケーションの考え方は、プログラムの構造(プロセス)と、ディレクトリ構造(モジュール)の両方に適用されます。

もっともシンプルな種類のアプリケーションはプロセスを一つも含みませんが、いくつかの機能を実装したモジュールで構成されます。このようなアプリケーションは、 ライブラリアプリケーション と呼ばれます。ライブラリアプリケーションのサンプルとしてはSTDLIBがあります。

プロセスを含むアプリケーションは、標準的なビヘイビアを使用して、監視ツリーとして実装するのが最も簡単です。

どのようにアプリケーションをプログラムしていくのか、ということについては、 アプリケーション の章で説明していきます。

1.4. リリース

リリース はErlang/OTPアプリケーションのサブセットと、ユーザ定義のアプリケーションから作成されます。

どのようにリリースをプログラムするのか、ということについては リリース の章で説明していきます。

対象となる環境にどのようにリリースをインストールするのか、ということについては、システム原則の中のターゲットシステムの章で説明していきます。

1.5. リリースのハンドリング

リリースのハンドリング というのは、実行中のシステムにおいて、異なるバージョンのリリースの間で、アップグレードしたり、ダウングレードしたりすることです。どのようにこれを行うのか、ということに関しては、 release handling の章で説明していきます。

Copyright (c) 1991-2009 Ericsson AB