このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

ErlangとJavaのあいだでリモートメッセージング (前編)

Erlangのプロセス間ではメッセージ通信が容易に行えますが、JInterfaceライブラリを使うと、ErlangプロセスとJavaプログラムとのあいだでもメッセージ通信ができます。予備知識である分散Erlangから説明し、JInterfaceを紹介します。ちょっと長いので、前後編に分けて、今回の前編ではErlang側の話をします。

内容:

  1. Erlangを分散モードで動かそう
  2. セキュリティソフトとマジッククッキー
  3. 分散ノード達を互いに接続する
  4. リモートメッセージングを試してみよう
  5. 今回のまとめと次回の予定

参考資料:

※ 単に「プロセス」と言った場合は、Erlang処理系内部で走る軽量プロセスのことです。OSが管理するプロセスは「OSプロセス」と呼ぶことにします。

Erlangを分散モードで動かそう

特にオプションを付けないerl(またはwerl)コマンドで、ERTS(Erlang RunTime System)を起動したときには、起動されたERTSはシングルノードのシステム、つまり他のノード(ERTS)と連携することなく単独/孤立状態で動作します。マルチノード・システムの構成員として起動するには、-name または -sname オプションを付けます。

  • erl -name nodename@host.domain.name
  • erl -sname nodename

以下では、-nameまたは-snameを付けて起動されたERTSの状態を分散モード、そうでないときは単独モードと呼ぶことにします(たぶんこれは、正式な用語法ではないでしょうが)。ERTS実行中に、単独モードと分散モードを切り替えることができますが、今回は触れません(net_kernelのmanページ http://www.erlang.org/doc/man/net_kernel.html を参照のこと)。

分散ノード(分散モードで動作するERTS)への名前付け方式には、ロングネーム方式とショートネーム方式があり、-nama ならロングネーム、-sname ならショートネームを指定しなければなりません。

ノード名は、ロングネームでもショートネームでも、

  • ノードのローカル識別名@ホスト名

の形式になります*1。ロングネームならホスト名として完全修飾ドメイン名(FQDN)を指定する必要があり、ショートネームなら修飾なしの短いホスト名が使用されます(非修飾ホスト名は自動的に付加される)。非修飾ホスト名は、(単独モードであっても)net_adm:localhost().で表示されます。

ロングネーム方式は少し面倒なので、以下ではショートネームを使います。インターネット上で広域的に分散運用するにはロングネームが必須ですが、同一マシン内で複数の分散ノードを立ち上げて実験するならショートネームで十分です。なお、ロングネームで名付けられノードとショートネームで名付けられたノードのあいだで通信はできない*2ので注意してください。

さて実際に、werl -sname first とすると:


Erlang (BEAM) emulator version 5.5.4 [async-threads:0]

Eshell V5.5.4 (abort with ^G)
(first@TP-X31-HIYAMA-2)1>

プロンプトにノード名(この場合は first@TP-X31-HIYAMA-2)が付くので、分散モードで動いていることは一目でわかります。node()コマンドは、そのノードのノード名を表示します。


(first@TP-X31-HIYAMA-2)1> node().
'first@TP-X31-HIYAMA-2'
(first@TP-X31-HIYAMA-2)2>

アットマークの右側のTP-X31-HIYAMA-2は、自動的に付加された、このマシンの非修飾ホスト名(実際はWindowsのコンピュータ名)です。


(first@TP-X31-HIYAMA-2)2> net_adm:localhost().
"TP-X31-HIYAMA-2"
(first@TP-X31-HIYAMA-2)3>

念のために注意しておくと、net_adm:localhost() の値は文字列ですが、node()が返すノード名は文字列ではなくて記号アトムです。アットマークはErlangの名前文字なのでアトム(記号名)として普通に使えます。ピリオドや数字が入るとき(ロングネーム使用時)はアトムをシングルクォートで囲みます*3

●セキュリティソフトとマジッククッキー

-name または -sname オプション付きでerl(またはwerl)を実行すると、ERTSはすぐにネットワークを触りにいきます*4。このとき、セキュリティソフトにブロックされるかもしれません。Erlangがネットワークにアクセスできるようにしてください。

もうひとつ、分散Erlangシステム(マルチノード・システム、クラスター)を構成するときにチェックすべきことに、マジッククッキーがあります。ERTSをはじめて分散モードで立ち上げると、立ち上げた(erl/werlコマンドを実行した)ユーザーのホームディレクトリに.erlang.cookieというファイルが自動的にできて、その内容がノードのマジッククッキー(秘密のおまじない)となります。Erlangシェルからは、erlnag:get_cookie().でクッキー(値はアトム)を参照できます。


(first@TP-X31-HIYAMA-2)3> erlang:get_cookie().
'SVVPNGSCAVVTFUTESKVX'
(first@TP-X31-HIYAMA-2)4>

.erlang.cookieファイル([lu].*xシステムではパーミッションが"read-only by user")を書き換えれば、もちろん、その内容がノードのクッキーに反映されます。同一の.erlang.cookieファイルを共有する分散ノードは同じクッキーを持ちます。

分散ノードが相互に通信するには、クッキーが同じであるか、通信相手のクッキーを前もって知っている/知らせている必要があります。今回は、同一ユーザーが2つ(以上)の分散ノードを同一コンピュータ内で立ち上げるので、クッキーを意識しなくても自動的に通信可能となります。一般的状況では、クッキー値の選択、クッキーファイルの置き場所、共有、転送(クッキー値の通知)、管理などに十分な注意が必要でしょう。詳細は、冒頭に挙げた資料や、-setcookie 起動時オプション、erlang:set_cookie/2 関数のmanページ(http://www.erlang.org/doc/man/erl.htmlhttp://www.erlang.org/doc/man/erlang.html)を参照してください。

●分散ノード達を互いに接続する

ではここで、二番目の分散ノードを次のように立ち上げます。

  • werl -sname second

念のため、クッキーを確認します。


Erlang (BEAM) emulator version 5.5.4 [async-threads:0]

Eshell V5.5.4 (abort with ^G)
(second@TP-X31-HIYAMA-2)1> erlang:get_cookie().
'SVVPNGSCAVVTFUTESKVX'
(second@TP-X31-HIYAMA-2)2>

2つの分散ノード'first@TP-X31-HIYAMA-2'と'second@TP-X31-HIYAMA-2'は同じクッキーを持っているので通信可能です。実際に繋がるかどうかを確認するには、通常のネットワークの場合と同様にpingコマンドを使います。


(second@TP-X31-HIYAMA-2)2> net_adm:ping('first@TP-X31-HIYAMA-2').
pong
(second@TP-X31-HIYAMA-2)3>

net_adm:ping/1は、引数で指定されたノードとの接続が成功するとpong、失敗すればpangを返します(ピン・ポン・パン)。接続が確立した他ノードをフレンドと呼び、フレンドノードのリストはnodes()で得られます。


(second@TP-X31-HIYAMA-2)2> nodes().
['first@TP-X31-HIYAMA-2']
(second@TP-X31-HIYAMA-2)3>

フレンド関係は対称的(片思いはなし)*5なので、secondはfirstのフレンドになっています。


(first@TP-X31-HIYAMA-2)4> nodes().
['second@TP-X31-HIYAMA-2']
(first@TP-X31-HIYAMA-2)5>

net_adm:ping/1以外に、net_kernel:connect_node/1でも接続(=フレンド関係)を確立できます。接続を切るにはerlang:disconnect_node/1を使います。(connectとdisconnectが別モジュールに入っているのがイヤなんですが、しょうがない。)

nodes/0よりもう少し詳しい情報が欲しいときは、net_adm:names/0があります。


(second@TP-X31-HIYAMA-2)3> net_adm:names().
{ok,[{"second",2317},{"first",2348}]}
(second@TP-X31-HIYAMA-2)4>

各ノードのローカル名と、使っているTCP/IPのポート番号が表示されます(ポート番号は処理系が適当に選んでいるようです)。このローカル名/ポート番号のリストを維持管理しているのは、ERTSとは別なOSプロセスであるEPMDErlang Port Mapper Daemon)です。EPMDは、(Erlang界隈で)well-knownなポート4369(tcp/udp両方使用)を開けています。EPMDは自動的に起動されるので通常は意識する必要はありません

●リモートメッセージングを試してみよう

ErlangのプロセスID(PID)は、単一ERTS内だけでなく、ネットワークワイドでも一意性が保証された識別子です。PIDを使えば、別な(ただしフレンド関係がある)ノードのプロセスへのメッセージ送信ができます。PIDによる通信はノード内ローカルでもネットワーク・グローバルでも透過的です。

とはいえ、他ノードにいるプロセスのIDを知ることは困難なので、実際にはプロセス指定に名前を使います。まずは、プロセスに名前を付けておく必要があります。それには、register/2を使います。次のプログラム(から作られるプロセス)を例に使いましょう。


%% receiver.erl
-module(receiver).
-compile(export_all).

start() ->
spawn(?MODULE, receiver_main, []).

receiver_main() ->
receive
stop -> % プロセス終了
io:fwrite("~w:bye bye.\n", [self()]),
exit(ok);
{message, Pid, Message} -> % データ受信
io:fwrite("Received! ===> From:~w Message:~s\n", [Pid, Message]);
Other -> % それ以外
io:fwrite("Oops! ~w is ignored.\n", [Other])
end,
receiver_main().

receiver:start()で作られたプロセスにrcvという名前を付けるには:


(first@TP-X31-HIYAMA-2)5> c(receiver).
{ok,receiver}
(first@TP-X31-HIYAMA-2)6> P = receiver:start().<0.43.0>
(first@TP-X31-HIYAMA-2)7> register(rcv, P).
true
(first@TP-X31-HIYAMA-2)8>

register(rcv, P).により登録した名前(registered name)rcvは、PIDの代わりに使えます。まずは、登録名をノード内ローカルに使ってみましょう。


(first@TP-X31-HIYAMA-2)8> rcv ! {message, self(), "Hello"}.
Received! ===> From:<0.36.0> Message:Hello
{message,<0.36.0>,"Hello"}
(first@TP-X31-HIYAMA-2)9> erlang:send(rcv, hi).
Oops! hi is ignored.
hi
(first@TP-X31-HIYAMA-2)10>

次に、別ノードsecond@TP-X31-HIYAMから、first@TP-X31-HIYAMA-2のプロセスrcvにメッセージを送ります。プロセス名rcvはfirst@TP-X31-HIYAMA-2内にローカルな名前なので、rcvを指定してもノードをまたいだ通信はできません*6


(second@TP-X31-HIYAMA-2)4> rcv ! {message, self(), "Hello"}.
** exited: {badarg,[{erl_eval,eval_op,3},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_loop,3}]} **

=ERROR REPORT==== 4-Jul-2007::09:05:40 ===
Error in process <0.36.0> on node 'second@TP-X31-HIYAMA-2' with exit value: {bad
arg,[{erl_eval,eval_op,3},{erl_eval,expr,5},{shell,exprs,6},{shell,eval_loop,3}]
}

(second@TP-X31-HIYAMA-2)5>

他ノードの名前付きプロセスを指定するには、{プロセス名, ノード名} というタプルを使います。


(second@TP-X31-HIYAMA-2)5> {rcv, 'first@TP-X31-HIYAMA-2'} ! {message, self(), "Hello"}.
{message,<0.38.0>,"Hello"}
(second@TP-X31-HIYAMA-2)6>

このとき、first@TP-X31-HIYAMA-2側では:


Received! ===> From:<7100.38.0> Message:Hello
(first@TP-X31-HIYAMA-2)10>

erlang:send/2を使っても同様な結果を得ます。

●今回のまとめと次回の予定

  1. Erlangを分散モードで動かすには、ノード(ERTS)に名前を付ける必要がある。
  2. ノードへの名前付けにはロングネームとショートネームがある。
  3. ロングネームは、「ノードのローカル識別名@ホストのFQDN」の形式、「-name ロングネーム」オプション。
  4. ショートネームは、「ノードのローカル識別名@被修飾ホスト名」の形式、「-sname ノードのローカル識別名」オプション
  5. 2つのノードが通信可能となるには、マジッククッキーが同一であるか、他ノードのマジッククッキーを知っている必要がある。
  6. デフォルトのマジッククッキーは、ユーザーのホームディレクトリの.erlang.cookieファイルに保存されている。
  7. 2つのノードが通信可能かどうかはnet_amd:ping/1で確認できる。
  8. 2つのノードの明示的な接続/切断には、net_kernel:connect_node/1とerlang:disconnect_node/1を使う。
  9. プロセスに(ノード内ローカルな)名前を付けるには、register/2を使う。
  10. registerにより付けたプロセス名はPIDと同じように使える。
  11. ネットワークワイドでプロセスを指定するには、{プロセス名, ノード名}というタプルを使う。このタプルもPIDと同じように使える。

以上、Erlangノード間でのメッセージ通信を説明しました。JInterfaceを使ったJavaプログラムも、Erlangノードと同じように分散Erlangシステムに参加できます。次回は、JInterfaceとJavaノードの話をします。

*1:ノードのローカル識別名を、Erlangではalivenameと呼ぶようです; Erlang nodenames consist of two components, an alivename and a hostname separated by '@'.

*2:それにもかかわらず、同一マシン上で、同じローカル識別名(@より左の部分)を持つロングネームとショートネームの同時使用は許されません。

*3:ピリオドを含むアトムをそのまま書いても構文エラーにはならないようですが。

*4:ショートネームを指定したとき、あるいはロングネームのFQDNに"127.0.0.1"を指定したときはローカル・ループバックを使うようなので、ほんとのネットワークを触るわけではありません。

*5:デフォルトの設定では、フレンド関係は推移的でもあり、フレンド関係を辺としたグラフは完全グラフになります。ただし、隠蔽ノードと呼ばれる例外もあります。

*6:たまたまsecond@TP-X31-HIYAM内にrcvがいれば、そのプロセスにメッセージが送られます。