Erlang 基礎ポイント7 - 分散処理
久しぶりのErlangですね。
今日は分散処理について勉強したいと思います。
Erlangで分散処理?
今までは一つのサーバで一つのErlangシェルを立ち上げて勉強しましたが、
実はErlangは複数のサーバがネットワークを作って、
大規模な動作をするのに適した仕組みを持っています。
簡単に複数のサーバをつなげて何かができるってことは楽しいですね。
Erlangノード
今マシンで動いている一つのErlangプログラムをErlangノードといいます。
正確にいうと、Erlangプログラムを動かしているErlangの仮想マシンをいいます。
JavaのJVMのようなものが立ち上がっていると考えればいいでしょう。
JVMは同じマシンで複数実行できますね。
同様にErlangも同じマシンで複数実行できます。
同じマシンでErlangプログラム2個実行した?
つまり、erlコマンドを2個実行したといったら、
"このマシンにはErlangノードが2つ立ち上がっている"といえます。
但し、erlコマンド実行し、Erlangプロセスが終了せず、ずっと動いていなければ、
立ち上がっているとはいえません。あくまで実行中のことが前提です。
Erlangノードについて説明したので、分散処理ノードの形態について
説明します。
分散処理ノードの形態
Erlangノードがどのネットワークのどのマシンにあるかによって、
分散処理ノードの形態は以下のように三つあります。
Erlangノードが同じマシン上にある
一つのマシン上に複数のErlangを実行して分散処理を行います。
一つマシンのリソースを分けて処理をするので、あまりパワーは出せないでしょう。
テスト用に分散処理を確認したい時にいいと思います。
Erlangノードがプライベートネットーワーク(LAN)上にある
社内ネットワークなので、セキュリティが安全ですし、複数のマシンを有効に活用できるので、
これが一番実用性があるのではないかと思います。
Erlangノードがインターネット上にある
インターネット上にある無数のマシンを束ねられることは魅力的ですが、
セキュリティの確保が難しいと思います。
でも、セキュリティにあまり気をつけなくてもいいすごく時間がかかる計算を
小さい処理単位に分けて、インターネット上のマシンで計算して結果を結合するといった用途に
合うかもしれない...と思います。
サンプル
準備
ここでは、プライベートネットーワーク(LAN)上にErlangノードをおくことを前提で、サンプルを作ってみます。
私は実PCを一台しか持っていないので、無料の仮想化ソフト「VirtualBox」を利用し、
Ubuntu 12.05が入った仮想PCを二台作ってから、サンプルを作成及び実行します。
Ubuntu 12.05はHomepage | Ubuntu Japanese Teamでダウンロードできます。
VirtualBox用のイメージが既にあるので、自分でインストールしなくていいです。
仮想PCを二つ作るにはVirtualBoxのクローン機能を使って、入手したイメージをコピーすればすぐです。
以下のように仮想PCを二つ作りました。
しかし、ソースをそれぞれの仮想PCで編集すると不便なので、
SSHで各仮想PCに接続してソースの編集を行います。
SSHクライアントはRLoginを使いました。
理解しやすくするため、サンプルは、echoサーバのようなものにします。
具体的には送信側がメッセージを受信側に送ると、
受信側は受信したメッセージに特定文字列を付加して受信側に返すものです。
以下、ソースです。
-module(echo). -export([start/0, send/1]). start() -> register(echo, spawn(fun() -> loop() end)). send(Msg) -> echo ! {self(), {server, Msg}}, receive {client, Res} -> Res end. loop() -> receive {From, {server, Msg}} -> From ! {client, string:concat("Echo response : ", Msg)}, loop() end.
分散処理を行う前にローカルでstart()関数とsend()関数を呼び出して動作確認をしてみましょう。
6> duke1@duke1-VirtualBox:~/erlang/blog$ erl Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false] Eshell V5.8.5 (abort with ^G) 1> c(echo). {ok,echo} 2> echo:start(). true 3> echo:send("Hello Erlang"). "Echo response : Hello Erlang" 4> echo:send("Hello Erlang"). "Echo response : Hello Erlang" 5> echo:send("Hello Erlang1"). "Echo response : Hello Erlang1" 6>
問題ないですね。
次は別々の仮想PCでErlangを動かして、分散処理をやってみます。
別々のマシンで分散処理
分散処理するすべてのErlangノードは同じバージョンのErlangと同じプログラムコードを
持つ必要があります。Erlangのバージョンを合わせるためにはそれぞれのマシンに同じバージョンのErlangをインストールするしかないでしょう。
しかし、プログラムコードの共有方法は以下のように色々あるようです。
ここでは、NFSを利用して上記のechoプログラムを2台の仮想PCで共有します。
NFSについては、ググればすぐ出ます。
機会があれば、コードサーバに関してブログを書いてみたいと思います。
まず、仮想PC 1号機・2号機のhostsファイルにそれぞれのホスト名とIPアドレスの対応を追加します。
例えば、1号機をvpc1.com(192.168.56.101)、2号機をvpc2.com(192.168.56.103)とするなら、以下のようにします。
192.168.56.101 vpc1.com 192.168.56.103 vpc2.com
次に仮想PC 1号機で以下のようにerlを起動します。
(物理PCや仮想PCに関わらず、ネットワーク状況によって以下の起動オプションでサンプルが動かないかも知れません。私の環境でしか確認していません。)
$ erl -name echoserver@vpc1.com -setcookie echocookie % -name サーバ名 : 呼出側がこのマシンで動くErlangプログラムを実行する時、識別のため、必要です。 % @の前にはErlangノード同士で識別する名前を書きます。 % @の後ろはhostsファイルに追加した名前(vpc1.com)を書きます。 % -setcookie クッキー名 : 分散処理をするErlang同士の認証に使われます。適当でいいと思いますが、すべてのErlangノードは同じクッキー名を使う必要があります。 Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false] Eshell V5.8.5 (abort with ^G) (echoserver@duke1-VirtualBox)1> c(echo). % コンパイルし、 {ok,echo} (echoserver@duke1-VirtualBox)2> echo:start(). % エコーサーバを起動します。 true
仮想PC 2号機では、以下のようにerlを起動します。
erl -name echoclient@vpc2.com -setcookie echocookie Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false] Eshell V5.8.5 (abort with ^G) (echoclient@vpc2.com)1> c(echo). % 2号機でもコンパイルします。 {ok,echo}
準備ができました。
仮想PC 2号機で仮想PC 1号機に対してリモート呼出をしてみましょう。
(echoclient@vpc2.com)1> rpc:call(echoserver@vpc1.com, echo, send, ["Hello"]). "Echo response : Hello"
できました!
他のマシンにあるErlangプログラムを呼び出す時は、rpcモジュールのcall関数を使います。
rpc:call(相手側のErlangノードのnameに指定した名前@相手側のErlangノードが入っているマシンのホスト名, モジュール名, 関数名, [引数1, 引数x...]).
他の言語にもあるSOAPやRPCのようで、大したものではないように見えるかも知れませんが、
プログラムや手順が簡単で無駄がないのが分かります。
この簡潔さなら、本来の処理にもっと集中できるのではないでしょうか。
追加(2014/09/26)
rpc:callを使うと、呼び出しが煩雑ですが、
以下のようにsend関数を修正すれば、もっと簡単にリモート呼出ができます。
send(Msg, Receiver) -> Receiver ! {self(), {server, Msg}}, receive {client, Res} -> Res end.
仮想PC 2号機から仮想PC 1号機への呼出は以下のようにすればいいです。
(echoclient@vpc2.com)2> echo:send("Hi VPC1", { echo, 'echoserver@vpc1.com' }). "Echo response : Hi VPC1" (echoclient@vpc2.com)3>
自分自身へはただのechoでいいですね。
もちろん、自分のところでもecho:start()を呼び出す必要があります。
(echoclient@vpc2.com)3> echo:start(). true (echoclient@vpc2.com)4> echo:send("Hi VPC1", echo). "Echo response : Hi VPC1"
要は、メッセージを送る時、プロセス名 ! メッセージを使いますが、
メッセージの受け取り側がリモートの場合は、プロセス名が{ プロセス名, 'サーバ名' }のように
タプルになるということです。
今日はここまで。
他にソケット通信やファイル、OTPなどがありますが、
これらはAPIの使い方みたいなものなので、基礎ポイントでは取り上げないつもりです。
別のタイトルで勉強しようかと思います。
参考
- 作者: Joe Armstrong,榊原一矢
- 出版社/メーカー: オーム社
- 発売日: 2008/02/23
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 284回
- この商品を含むブログ (97件) を見る