書いてみた。どうも綺麗に書けてる気がしないのだが。(関数型の書き方に慣れてないのか……) ソースコードは末尾に。
HTTP のリクエストを並列に投げる部分はこんな感じで。
- worker(): manager から URI を受け取って http:request()、結果を manager に戻す
- manager(): workerプロセスのリストを受け取って、それぞれの workerに仕事をやらせる / 完了したリクエストを数える / timer 処理
- timer(): start / stop 間の経過時間を計る
複数ノード間で分散処理をするために必要な設定。
$ erl -name test Erlang (BEAM) emulator version 5.5 [source] [async-threads:0] [hipe] Eshell V5.5 (abort with ^G) (test@fc5.internal)1> net_adm:ping('test@fc5.internal'). pong
test@fc5.internal というのがノード名。net_adm:ping() でノードが生きているかどうかをチェック。生きていれば pong、死んでいれば (通信できなければ) pang が返る。
この状態で spawn の引数にノード名を付けてやれば、リモートノードでプロセスが生成される。
リモートノードでモジュールを load する方法。
deploy(Nodes) -> { Mod, Bin, FName } = code:get_object_code(?MODULE), lists:foreach( fun(Node) -> rpc:call( Node, code, load_binary, [ Mod, FName, Bin ] ), rpc:call( Node, ?MODULE, init, []) end, Nodes ).
code:get_object_code(モジュール名) で得た結果を、rpc:call でリモートノードに投げて code:load_binary() に渡す。これでモジュールがリモートになくても大丈夫。
ということで、実行してみる。
% 2node でモジュール読み込み 1> erab:deploy(['test@fc5.internal', 'test@labs.internal']). ok % 2node で実行 2> erab:main(['test@fc5.internal', 'test@labs.internal'], "https://kutani/", { 10, 1000 }). started elapsed time: 20.2269 (sec) % 1node で実行 3> erab:main(['test@labs.internal'], "https://kutani/", { 10, 1000 }). started elapsed time: 39.5101 (sec) % node 追加 4> erab:deploy(['test@fc6.internal']). % 3node で実行 5> erab:main(['test@fc6.internal', 'test@fc5.internal', 'test@labs.internal'], "https://kutani/", { 10, 1000 }). started elapsed time: 13.7471 (sec)
1node で 25 req/sec. 3node で 77 req/sec.
サーバのログには以下のように、3ホストからリクエストが来てる様子が残っている。
192.168.0.142 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.115 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.149 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.142 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.115 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.149 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044 192.168.0.142 - - [29/May/2007:17:33:36 +0900] "GET / HTTP/1.1" 403 5044
……で、これでサーバの CPU を100%使わせられたか、というと。
ab は単独で 60req/sec 出せたのに、erab では 25req/sec しか出せないこともあって……erlangのノードが 10ぐらい必要そうだという落ちで。そんなに自由になるマシン無いよ。
-module(erab). -compile(export_all). worker() -> receive { URI, Manager } -> { ok, _ } = http:request( URI ), Manager ! { ok, self() }, worker(); die -> died end. manager( URI, 0, Timer ) -> Timer ! stop, Timer ! result, finished; manager( URI, Num, Timer ) -> receive { start, Clients } -> Timer ! start, lists:foreach( fun( Client ) -> Client ! { URI, self() } end, Clients ), manager( URI, Num, Timer ); { ok, Client } -> if Num > 1 -> Client ! { URI, self() }, manager( URI, Num-1, Timer ); true -> Client ! die, manager( URI, 0, Timer ) end end. timer( Start, End ) -> receive start -> timer( erlang:now(), {} ); stop -> timer( Start, erlang:now() ); result -> Diff = timer:now_diff( End, Start ), io:format("elapsed time: ~p (sec)~n", [ Diff / 1000000 ]) end. run( URI, Clients, N ) -> Timer = spawn( ?MODULE, timer, [ {}, {} ] ), Manager = spawn( ?MODULE, manager, [ URI, N, Timer ] ), Manager ! { start, Clients }, started. main( Nodes, URI, { C, N } ) -> Clients = lists:map( fun(Node) -> lists:map( fun(_) -> spawn(Node, ?MODULE, worker, []) end, lists:seq( 1, C ) ) end, Nodes ), run( URI, lists:flatten(Clients), N ). main( URI, { C, N } ) -> Clients = lists:map( fun(_) -> spawn(?MODULE, worker, []) end, lists:seq( 1, C ) ), run( URI, Clients, N ). init() -> application:start(inets), application:start(ssl). deploy(Nodes) -> { Mod, Bin, FName } = code:get_object_code(?MODULE), lists:foreach( fun(Node) -> rpc:call( Node, code, load_binary, [ Mod, FName, Bin ] ), rpc:call( Node, ?MODULE, init, []) end, Nodes ).