非同期処理の通知処理 with tatsumaki

56
非非非非非非非非非非 with Tatsumaki 笑笑笑笑 笑笑笑笑 笑笑笑笑笑 笑笑笑笑笑笑 、、、 20 笑笑 SlideShare 笑笑笑笑笑笑笑笑笑笑笑笑笑笑20 笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑 笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑 。。 笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑笑 体。 keroyonn / Hokkaido.pm YAPC::Asia 2010

Upload: keroyonn

Post on 11-Jun-2015

4.070 views

Category:

Documents


1 download

DESCRIPTION

うまく変換できない箇所(ほぼネタ部分)はカットしました。20枚も減ってしまったた

TRANSCRIPT

Page 1: 非同期処理の通知処理 with Tatsumaki

非同期処理の通知処理 with Tatsumaki

笑いあり、涙あり、萌えあり、ポロリありの 20 分間

SlideShare 版ではうまく変換できないため、 20 枚に及ぶネタ的部分をカットしました。微妙に意味が通らない記述があったらごめんなさい。全体の雰囲気はいずれ公式に公開されるであろう動画版でお楽しみください。

keroyonn / Hokkaido.pm

YAPC::Asia 2010

Page 2: 非同期処理の通知処理 with Tatsumaki

自己紹介

WEB 系プログラマ in Sapporo 仕事のメイン: Flex/ExtJS 趣味のメイン: C# + WPF サーバーサイド : Perl

ちょっとずつ違う素敵な ID 達 cpan : keroyon http://search.cpan.org/~keroyon/ github : keroyonnhttp://github.com/keroyonn twitter : keroyonn_ http://twitter.com/keroyonn_ hatena :keroyon0630

http://d.hatena.ne.jp/keroyon0630

YAPC::Asia 2010

Page 3: 非同期処理の通知処理 with Tatsumaki

今日のお題

WEB 越しにガンガン登録される重いタスクを1. リアルタイムに2. 待ち時間を感じさせずに3. リソースを食い潰されないように処理するにはどうすればいいか

対象者 : 非同期サーバーとか触ったことない方 (Plack 自体を触ったことなくてもギリギリ OK) 自分はバリバリだけど、初心者にうまいこと説明する

の難しいなと思ってる方 ポロリを見たい方

YAPC::Asia 2010

Page 4: 非同期処理の通知処理 with Tatsumaki

第 1 話: みんな大好き CGIクライアントの非同期化にチャレンジする

YAPC::Asia 2010

Page 5: 非同期処理の通知処理 with Tatsumaki

ブロッキング && ストリーミング with CGI

YAPC::Asia 2010

■ その 1 ブロッキング、ストリーミングとは その 2 Hello World ストリーミング with CGI その 3 クライアント側を非同期化してみる

Page 6: 非同期処理の通知処理 with Tatsumaki

その1 : ブロッキング、 ストリーミングとは クライアント

ブロッキング : Ajax とか使わないので、 UI がブロックされる( 同期処理 ) ボタンが反応しなくて全部固まるよ !

ストリーミング : 何らかのフォーマットでデータを読み取り続ける ( 切断しない )Flash → XMLSocketAjax → multipart/mixed

サーバー ブロッキング : プロセス数 = 同時接続数 (prefork なら )

( 同期処理 ) サーバープロセスひとつで同時に 1 リクエストまでしかさばけないよ

ストリーミング : データを流し続ける or 特定のイベントでデータを流す

YAPC::Asia 2010

Page 7: 非同期処理の通知処理 with Tatsumaki

ブロッキング && ストリーミング with CGI

YAPC::Asia 2010

その 1 ブロッキング、ストリーミングとは■ その 2 Hello World ストリーミング with CGI その 3 クライアント側を非同期化してみる

Page 8: 非同期処理の通知処理 with Tatsumaki

その 2 : HelloWorld ストリーミング with CGI

ソースコード#!perl$| = 1;print "Content-Type: text/html\n\n";for (1..5) {

print "<h2>Hello world!</h2>\n";sleep 1;

}

結果hellohello…5 回繰り返す…

YAPC::Asia 2010

Page 9: 非同期処理の通知処理 with Tatsumaki

ブロッキング && ストリーミング with CGI

YAPC::Asia 2010

その 1 ブロッキング、ストリーミングとは その 2 Hello World ストリーミング with CGI■ その 3 クライアント側を非同期化してみる

Page 10: 非同期処理の通知処理 with Tatsumaki

一つ目 :long-polling

タイムアウトを長めに取って、XHRする。成功か失敗時にまたXHRするだけ。<html><head> <title>long-polling 1</title> <script type="text/javascript" src="./js/jquery-1.3.2.min.js"></script> <script> $(function connect() { $.ajax({ type: 'GET', dataType: 'html', url : '/yapc2010/hello_cgi_1.cgi', timeout : 30000, success : function(result) { $(‘#message’).append(result); /* なんか処理 */ }, complete : function() { setTimeout(connect, 100); /* 接続 or タイムアウトで再接続 */} }); }); </script> </head> <body> <h1>long-polling その1</h1> <div id="message"/> </body> </html> ちゃんとした例は、jquery.ev.js を見てください。

YAPC::Asia 2010

Page 11: 非同期処理の通知処理 with Tatsumaki

結果

Hello World '1‘Hello World '2‘Hello World '3‘Hello World '4‘Hello World '5‘--- ここまでが4秒後にドバっと出てくる

Hello World '1‘Hello World '2‘Hello World '3‘Hello World '4‘Hello World '5‘--- ここまでが4秒後にドバっと出てくる

以降繰り返しYAPC::Asia 2010

Page 12: 非同期処理の通知処理 with Tatsumaki

HelloWorld ストリーミング with CGI (long-polling 対応版 )

#!perlopen my $fh, '<', 'counter.txt';my $count = <$fh>;close $fh;$count++;

open my $fh, '>', 'counter.txt';print $fh $count;close $fh;

print "Content-Type: text/html\n\n";print "<h2>Hello World '$count'</h2>\n";

YAPC::Asia 2010

Page 13: 非同期処理の通知処理 with Tatsumaki

結果

クライアント毎に見える数値が全て変る。 対策▪ クライアント毎に固有のFIFOなキューを作って、「Hello」を

書き込む。▪ そして接続される度にFlushする。

※ Tatsumaki::MessageQueue を使うと各クライアント毎に、 次の接続時に出力すべきメッセージ」を管理してくれる Aさんは次Hello5、Bさんは次Hello3とか。

毎回接続が切れているけれど、ストリーミングと言えるのか。 ほぼリアルタイムに取得できるが再接続の時間でやや遅くな

る → 更新頻度が少なければ分からない

YAPC::Asia 2010

Page 14: 非同期処理の通知処理 with Tatsumaki

二つ目 :multipart/mixed

バウンダリで複数のContent-Type を区切った専用フォーマットでレスポンスを返す

実際のフォーマット例HTTP/1.0 200 OKContent-Type: multipart/mixed; boundary="ABC"

--ABCContent-Type: text/html

<h2>Hello World '1'</h2>--ABCContent-Type: text/html <h2>Hello World '2'</h2>--ABCContent-Type: text/html <h2>Hello World '3'</h2>--ABC

YAPC::Asia 2010

Page 15: 非同期処理の通知処理 with Tatsumaki

サーバー側を multipart/mixed 対応にしてみる

#!perl $|=1;print qq{Content-Type: multipart/mixed;

boundary="ABC"\n\n};

print "--ABC\n";for (1..5) {

print "Content-Type: text/html\n\n";print "<h2>Hello World '$_'</h2>\n";print "--ABC\n";sleep 1;

}YAPC::Asia 2010

Page 16: 非同期処理の通知処理 with Tatsumaki

クライアント側を DUI.js で処理するXHRで multipart/mixed を読み込んでくれる <html> <head> <title>multipart/mixed</title> <script type="text/javascript" src="./js/jquery-1.3.2.min.js"></script> <script type="text/javascript" src="./js/DUI.js"></script> <script type="text/javascript" src="./js/Stream.js"></script> <script> $(function (){ var s = new DUI.Stream(); s.listen(‘text/html’, function (result) { // Content-Typeがhtmlのものを取得してresultへ onNewData(result); }); s.load('/yapc2010/hello_cgi_3.cgi'); });

function onNewData(data) { $(‘#message’).append(data); // 適当に出力してみる } </script> </head> <body> <h1>multipart/mixed</h1> <div id="message"/> </body> </html>

YAPC::Asia 2010

Page 17: 非同期処理の通知処理 with Tatsumaki

結論

ストリーミングは C10K 問題の影響を受けすぎる

アクセス数と頻度 :10分間で1,000アクセスあり、平均して散らばっている

プロセスサイズ : 30メガとする

通常 処理時間 : 1リクエスト /秒 プロセス数 : 16 メモリ使用量 : 16プロセス × 30M = 480M

ストリーミング 処理時間 : 1リクエスト /10分 プロセス数 : 1,000(60倍以上) メモリ使用量 : 1,000プロセス × 30M = 3テラ!

YAPC::Asia 2010

Page 18: 非同期処理の通知処理 with Tatsumaki

第 2 話:「 PSGI/Plack ストリーミング」

サーバーの非同期化にチャレンジする

YAPC::Asia 2010

Page 19: 非同期処理の通知処理 with Tatsumaki

ノンブロッキング && ストリーミング with PSGI/Plack

YAPC::Asia 2010

■ その 1 ノンブロッキング、ストリーミングとは その 2 PSGI/Plack のおさらい その 3 PSGI/Plack ストリーミング仕様

Page 20: 非同期処理の通知処理 with Tatsumaki

ノンブロッキング && ストリーミング with PSGI/Plack

サーバーノンブロッキング 1 プロセス && 1 スレッドで、同時

接続数いくらでも = 省メモリ

ストリーミング : データを流し続ける or 特定のイベントでデータを流す

YAPC::Asia 2010

Page 21: 非同期処理の通知処理 with Tatsumaki

ノンブロッキング && ストリーミング with PSGI/Plack

YAPC::Asia 2010

その 1 ノンブロッキング、ストリーミングとは■ その 2 PSGI/Plack のおさらい その 3 PSGI/Plack ストリーミング仕様

Page 22: 非同期処理の通知処理 with Tatsumaki

Plack おさらい その 1:Hello PSGI/Plack ソースコード(簡略版)

これじゃ、何だか分からないhello.psgisub {

[200,['Content-Type' => 'text/html'],['<h2>Hello world Plack!</h2>'],]

}

ソースコード(冗長版)でも、これ以上分かりやすくもならないhello.psgimy $your_psgi_app = sub { ← PSGIアプリはコードリファレンス

my $response = [200,['Content-Type' => 'text/html'],['<h2>Hello world Plack!</h2>'],];return $response;

};$your_psgi_app; ← do でファイルが読み込まれるため、最後に評価された式が返る

YAPC::Asia 2010

Page 23: 非同期処理の通知処理 with Tatsumaki

Plack おさらい その 2: Plack のクラス関係

PSGI アプリの実行plackup –a “your plack application”

Plack::Runner Plack::Loader

HTTP::Server::PSGI

接続を待受配列を書き出し

Plack::Loader$app = do "Your Plack

Application";

Plack::Runnerブートストラップ

Plack::Util便利な命令

YAPC::Asia 2010

Page 24: 非同期処理の通知処理 with Tatsumaki

Plack おさらい その 3:HTTP::Server::PSGI コード解説基本 : 接続を待受 → アプリ実行 → 配列書き出し

1. accept_loop 接続待受sub accept_loop {

if (my $conn = $self->{listen_sock}->accept) {$self->handle_connection($env, $conn, $app);

}}

2. handle_connection “Your Plack Application” を実行 → 配列に。

sub handle_connection {my($self, $env, $conn, $app) = @_;$res = Plack::Util::run_app $app, $env;$self->_handle_response($res, $conn);

}

YAPC::Asia 2010

Page 25: 非同期処理の通知処理 with Tatsumaki

Plack おさらい その 3:HTTP::Server::PSGI コード解説3. _handle_response 配列を書き出し

sub _handle_response {my($self, $res, $conn) = @_;Plack::Util::header_iter($res->[1], sub {

my ($k, $v) = @_;push @lines, "$k: $v\015\012";

});unshift @lines, "HTTP/1.0 $res->[0] @{[ HTTP::Status::status_message($res-

>[0]) ]}\015\012“$self->write_all($conn, join('', @lines), $self->{timeout})if (defined $res->[2]) {

Plack::Util::foreach($res->[2],sub { $self->write_all($conn, $_[0], $self->{timeout}) },);

}}

YAPC::Asia 2010

Page 26: 非同期処理の通知処理 with Tatsumaki

ノンブロッキング && ストリーミング with PSGI/Plack

YAPC::Asia 2010

その 1 ノンブロッキング、ストリーミングとは その 2 PSGI/Plack のおさらい■ その 3 PSGI/Plack ストリーミング仕様

Page 27: 非同期処理の通知処理 with Tatsumaki

Hello World PSGI/Plackストリーミングuse Plack::Middleware::Static;use AnyEvent;my $your_psgi_app = sub { # コードリファレンスを作って my $env = shift; my $your_streaming_app = sub { # コードリファレンスを作って my ($responder) = @_; # コードリファレンスを受け取る # Twiggy::Writer を取得 my $writer = $responder->([ 200, [ 'Content-Type' => 'multipart/mixed;

boundary="ABC"' ] ]); my $counter = 1; $writer->write("--ABC\n"); my $timer;$timer = AE::timer 0, 0.5, sub { # 0.5 秒おきに HelloWorld $writer->write("Content-Type: text/html\n\n"); $writer->write(“<h2>Hello World ‘${counter}’</h2>\n”); # Twiggy::Writer->write を実

行 $writer->write("--ABC\n"); if ( ++$counter > 5 ) { undef $timer; } }; }; return $your_streaming_app; # 返す }; $your_psgi_app = Plack::Middleware::Static->wrap( $your_psgi_app, path => sub { s{^/static/}

{} }, root => ‘./htdocs/’ ); $your_psgi_app; # 返す

YAPC::Asia 2010

Page 28: 非同期処理の通知処理 with Tatsumaki

PSGI/Plack ストリーミングのクラス関係

PSGI アプリの実行plackup –a “your plack application”

Plack::Runner Plack::LoaderTwiggy

接続を待ち受け配列を書き出し

Plack::Loader$app = do "Your Plack

Application";

Plack::Runnerブートストラップ

Plack::Util便利な命令

YAPC::Asia 2010

Page 29: 非同期処理の通知処理 with Tatsumaki

Twiggy のコード解説 #1

1. _create_tcp_server tcp サーバーを生成 sub _create_tcp_server { return tcp_server $host, $port, $self->_accept_handler($app,

$is_tcp), $self->_accept_prepare_handler; } ポイント : AnyEvent::Socket::tcp_server は非同期。 2. _accecpt_handler _run_app を実行 sub _accept_handler { my ( $self, $app, $is_tcp ) = @_; return sub { my ( $sock, $peer_host, $peer_port ) = @_; $self->_run_app($app, $env, $sock); }; } ポイント : _run_app を実行するだけ。

YAPC::Asia 2010

Page 30: 非同期処理の通知処理 with Tatsumaki

Twiggy のコード解説 #2

3. _run_app アプリを実行 sub _run_app { my($self, $app, $env, $sock) = @_; my $res = Plack::Util::run_app $app, $env; if ( ref $res eq 'ARRAY' ) { # 配列リファレンスなら通常通り $self->_write_psgi_response($sock, $res); } elsif ( ref $res eq 'CODE' ) { $res->( # コードなら、以下の関数を渡して実行 sub { my $res = shift; if ( @$res == 2 ) { # 実行結果の配列要素数が 2 my ( $status, $headers ) = @$res; my $writer = Twiggy::Writer->new($sock, $self->{exit_guard}); my $buf = $self->_format_headers($status, $headers); $writer->write($$buf); # 非同期にヘッダだけ書き込んで、 Twiggy::Writer を返却 return $writer; } else { # 実行結果の配列要素数が 3 つか 4 つ my ( $status, $headers, $body, $post ) = @$res; my $cv = $self->_write_psgi_response($sock, [ $status, $headers, $body ]); $cv->cb(sub { $post->() }) if $post; } } ); } }

YAPC::Asia 2010

ポイント : ここまでの間、 IO は非同期しか使ってない

Page 31: 非同期処理の通知処理 with Tatsumaki

大量のアクセスを捌くと言われる非道鬼天皇 : その強さの秘密とは?3つのひみつ 究極の受け流し性能 : 自分では何もせず、右から左へ受け流す

強力な 3 つの下僕達 : あらゆる処理を代りにこなす忠実な下僕

萌え萌えな容姿 : キャラクターグッズが出るくらいの萌え

YAPC::Asia 2010

Page 32: 非同期処理の通知処理 with Tatsumaki

大量のアクセスを捌くと言われる非道鬼天皇 : その弱点とは?

3つの弱点

同期 IO 処理 : 一瞬でもブロックしてはいけない。 毎回非同期 IO 処理を手で書くの?

CPU 使う処理 : CPU 処理でブロックされる場合ももちろんダメ。 全部自分で fork するの? そもそも重い処理は別サーバーにしたいけ

ど。。。

書きづらい : 低レベルの処理が剥き出しになっている。 いちいちディスパッチするコード書くの? multipart/mixed をいちいち手で出力さ

せるの?

YAPC::Asia 2010

Page 33: 非同期処理の通知処理 with Tatsumaki

第 3 話:三つの下僕に命令だ !!モジュールを利用して非同期プログラミングを実用的に行う

YAPC::Asia 2010

Page 34: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 1: Tatsumakiその特徴「書きづらい」という欠点を補う 非同期 WEB アプリケーションフレームワーク

ディスパッチ処理 multipart/mixed の自動生成 static ファイル配信 メッセージキュー

YAPC::Asia 2010

Page 35: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 1: TatsumakiHello World ストリーミング with Tatsumaki

package Hello; use base ‘Tatsumaki::Handler’; # Tatsumaki::Handler を継承 use AnyEvent; use Tatsumaki::Application; __PACKAGE__->asynchronous(1); # 非同期モードに設定 sub get { # リクエストメソッドに応じて自動実行 my ( $self, $arg ) = @_; $self->multipart_xhr_push(1); # 自動でバウンダリなどを生成する my $counter = 1; my $timer; $timer = AE::timer 0, 0.5, sub { # リファレンスを渡すと自動で JSON にエンコードして書き込む $self->stream_write( { message => "<h2>Hello World ${counter}</h2>" } ); if ( ++$counter > 5 ) { undef $timer; $self->finish; } }; } my $your_tatsumaki_app = Tatsumaki::Application->new( [ ‘/’ => ‘Hello’ ] ); #

ディスパッチも簡単 $your_tatsumaki_app->static_path(‘./htdocs/’); # 静的ファイルも配信

$your_tatsumaki_app; # いつも通り PSGI アプリを返そう。コードリファレンスの中でコードリファレンスを返して、その中で受け取ったコードリファレンスに配列を渡して実行…をやってくれる

YAPC::Asia 2010

Page 36: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 1: Tatsumaki まとめ Tatsumaki::Application

複数のハンドラをディスパッチ static_path メソッドで静的コンテンツ配信

Tatsumaki::Handler リクエストメソッドに対応して get/post 関数を実行してくれる multipart/mixed を自動で作成してくれる リファレンスを書き出すと、自動で JSON にエンコードしてくれる

Tatsumaki::MessageQueue 各クライアント毎に、「次の接続時に出力すべきメッセージ」を管理

してくれる ※今回は説明しません。

YAPC::Asia 2010

Page 37: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanその特徴 重い CPU 処理が苦手という欠点を補うジョブキューサーバー

登録しておいて後でゆっくりではなく、即時に処理してくれる 処理の進捗状態をクライアントに伝えることができる

ワーカー : サーバーにジョブ名を登録する。 タスク ( コードリファレンス ) をジョブ名に紐付けておく サーバーからジョブと引数を取って来て、実行する

サーバー : クライアントから依頼された仕事をキューに溜めておく ワーカーから問合せがあったら、キューからジョブと引数を取ってきて渡す

クライアント : サーバーにジョブ名と引数を渡して処理を依頼する

YAPC::Asia 2010

ジョブ依頼

ジョブ取得 結果

結果

ジョブ登録

Page 38: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanデモ1. ワーカーから CAN_DO(1) パケットを送って、 HELLO というジョブを処理できるよ ! とサーバーに教える perl -e 'print "\0REQ" . pack("NN", 1, 5) . "HELLO"' | nc 0 4730

status : HELLO 0 0 1

2. クライアントから SUBMIT_JOB(7) パケットを送って、 HELLO(xxx) というジョブを処理してね ! とサーバーに伝えて、 キューイングしてもらう perl -e 'print "\0REQ" . pack("NN", 7, 10) . "HELLO\0\0xxx"' | nc 0 4730

status : HELLO 1 0 1

3. ワーカーから GRAB_JOB(9) 自分の処理できるジョブを頂戴 ! とサーバーに伝えて、 ジョブ名 HELLO と、引数 xxx をサーバーから受け取る perl -e 'print "\0REQ" . pack("NN", 9, 5) . "HELLO"' | nc 0 4730

※CAN_DO をしたのと別接続になっちゃっているので、 NO_JOB というのが返ってくる

YAPC::Asia 2010

Page 39: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: GearmanHello World with Gearman

ジョブを定義する

ジョブクラス : MyJobs.pm hello を秒間 1 回、計 5 回出力する。 job_* で始まる単なる関数

package MyJobs; sub job_hello { my $job = shift; my $arg = $job->arg; # クライアントが設定した引数 for (1..5) { print "hello $arg $_\n"; sleep 1; $job->set_status($_, 5); # 進捗ステータスを更新 } return “finished:$arg”;   # 完了したらリターン } 1;

YAPC::Asia 2010

Page 40: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanワーカーサーバー #1 use strict; use warnings; use Gearman::Worker; # 同期式ワーカーライブラリ use Getopt::Long; use Parallel::Prefork; use Pod::Usage; use UNIVERSAL::require; use Class::Inspector; use Data::Dumper; my $max_workers = 10; GetOptions( 's|server=s@‘ => \my $servers, 'prefix=s‘ => \my $prefix, 'max-workers=i‘ => \$max_workers, 'h|help‘ => \my $help, ) or pod2usage(); pod2usage() unless $servers && @$servers; pod2usage() if $help;

YAPC::Asia 2010

Page 41: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanワーカーサーバー #2 my $worker = Gearman::Worker->new(); $worker->job_servers(@$servers); # サーバーの IP を登録する $worker->prefix($prefix) if $prefix; for my $klass (@ARGV) { $klass->use or die $@; my @jobs = grep /^job_/, @{ Class::Inspector->functions($klass) }; for my $job (@jobs) { ( my $job_name = $job ) =~ s/^job_//; # ジョブを登録する $worker->register_function( $job_name, $klass->can($job) ); } }

YAPC::Asia 2010

Page 42: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanワーカーサーバー #3 my $pm = Parallel::Prefork->new({ max_workers => $max_workers, trap_signals => { TERM => 'TERM', HUP => 'TERM', USR1 => undef, }, }); while ( $pm->signal_received ne 'TERM' ) { $pm->start and next; $worker->work; # ジョブを実行する $pm->finish; } $pm->wait_all_children();

YAPC::Asia 2010

Page 43: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanクライアントuse AnyEvent::Gearman; # 非同期クライアントライブラリmy $cv = AE::cv;my $client = gearman_client '127.0.0.1:4730';for (1..10) { $cv->begin; $client->add_task( hello => “[$_]”, # ジョブ名 => 引数 on_complete => sub { # ジョブの戻り値を受け取ることができる my $result = $_[1]; print "complete => $result\n"; $cv->end; }, on_status => sub { # ジョブの進捗状況を受け取ることができる my ($self, $n, $d) = @_; print "status => $n:$d\n"; }, );}$cv->recv; # タスクの終了を待ち受ける必要がある

YAPC::Asia 2010

Page 44: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanデモジョブを 10個投入してみるこの Hello World Gearman は 1 ジョブで 5 秒かかる

従ってワーカーの並列数を 10個にすると、 5 秒かかる

ワーカーの並列数を 5個にすると、 10 秒かかる

YAPC::Asia 2010

Page 45: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 2: Gearmanまとめ ワーカー

手動でたくさん立ち上げるのは管理が面倒なので、 スタートアップ用のスクリプトを作っておくとよい register_function でジョブを登録し、コードリファレンスと紐付ける work でジョブをグラブして実行する

クライアント クライアント側 (Tatsumaki) は、非同期とした方がよいため、 AnyEvent::Gearman を使う。ワーカー側はその限りではない add_task で、ジョブをキューイングし、ハンドラで終了を待ち受ける AnyEvent のループを回す必要

YAPC::Asia 2010

Page 46: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 3: WebService::Asyncその特徴 非同期 IO 処理を毎回手で書かなくちゃいけないという 欠点を補うためのモジュール ※ 三つないと“ごろ”が悪いので作った。 じゃないと、三つの下僕にならないじゃん。

Tatsumaki::HTTPClient にはない各種の機能 非同期であるにも関わらず、 WebService::Simple ばりに簡単 キャッシング レスポンスのパース レスポンスの変換 (Text::MicroTemplate で XML にするとか ) リトライ処理 詳細なロギング 結果はコールバックとしても、戻り値としても処理可能 複数のリクエストとレスポンスの対応付けを柔軟に処理可能 柔軟にカスタマイズ可能

YAPC::Asia 2010

Page 47: 非同期処理の通知処理 with Tatsumaki

三つの下僕 その 3: WebService::Async使い方 use WebService::Async; use WebService::Async::Parser::JSON; my $wa = WebService::Async->new( base_url => 'http://ajax.googleapis.com/ajax/services/language/translate', param => { v => '1.0', langpair => 'en|it' }, response_parser => WebService::Async::Parser::JSON->new, # パーサー

は JSON on_done => sub { # ひとつのリクエスト

が完了 my ($service, $id, $res, $req) = @_; print $req->param->{'q'} . " => "; print "$res->{responseData}->{translatedText}\n"; }, ); $wa->add_get( q => 'apple' ); $wa->add_get( q => 'orange' ); $wa->add_get( q => 'banana', langpair => 'en|fr' ); $wa->send_request; #パラレルにリクエス

トを発行

結果 orange => arancione banana => la banane apple => mela

YAPC::Asia 2010

Page 48: 非同期処理の通知処理 with Tatsumaki

最終話: Flex でサービスを構築するには

例 . 「ぶつぶつの森 ~ ぶつぶつ交換で始まる愛もある」リアル知人をターゲットにした 所有メディア登録→飲み会予約→ぶつぶつ交換システム

YAPC::Asia 2010

Page 49: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」: 要件など要件 (抜粋 )

会員の所有物 (主に書籍、音楽などのメディア ) をカタログ化して PC 、携帯、印刷物上で見られるようにする。 デジタル書籍 /音楽に関してはファイル名一覧を送ると、 Amazon 、楽天、 Wikipedia などを

マッシュアップしてカタログ化する ユーザー登録時に一括で所有書籍を登録してもらう 処理中には処理が完了したものから随時一覧を表示し、クリックで詳細に飛べるようにする 処理が完了すると、 zip 圧縮されたカタログ (PDF など ) がダウンロード可能となる

設計 複数ファイルを選択してファイル名を送信する必要がある → Flash(Flex/Air) 処理結果をリアルタイムでクライアントに送る必要がある → XMLSocket によるストリーミング ( 例えば )HTML を生成して ZIP圧縮する必要があるため CPU を消費する → Gearman による分散処理

実装のポイント ソケットポリシーファイルのサーバが必要 Flex 側は HTTP のヘッダを自前で生成 /解析する Tatsumaki 側は、 XMLSocket を作ってはくれない。自前で XML にしてヌルバイトを付加する必要。 Gearman のキューイングされている待ちタスクの数を取得する ワーカー側での set_status によるローディングの表現

YAPC::Asia 2010

Page 50: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」: 全体の仕組み

YAPC::Asia 2010

Twiggy/TatsumakiGearman クライアン

ト[AnyEvent::Gearma

n]

Gearman

サーバー

Flash(Flex/Air)・ファイル一覧取得 (FileReferenceList)・ヘッダ生成・レスポンス解析・ローディング表示

Gearmanワーカー

[Gearman::Worker]処理中

ローディングを提供処理待ち ( キュー )待ちの数を提供

待ち1 待ち

3

待ち2

処理1

処理2 処理

3

1 2 3

on_status

set_status

on_complete

return

ファイル選択

on_data

DB一覧表

1

2

3

45

6

7

Page 51: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」実装その 1 : サーバ設置とリクエスト生成 (Flex)ソケットポリシーファイルサーバの設置 $ cpanm AnyEvent::FlashSocketPolicy $ su - % flash-socket-policy-server --domain-policy=master-only \ --domain='*' \ --to-ports=500 ※843番ポートで起動されます。

XMLSocket は XHR のような同一ドメイン、同一ポートの制限はない。代りに、ソケットポリシーサーバが必要 データ通信する前に必ずポリシーサーバからファイルを受け取る必要があるため、結構な回数の通信が行われる

Flex 側からリクエストする リクエストヘッダを自分で作れば OK -Connect.as- public function connect():void {

var socket:XMLSocket = new XMLSocket;socket.addEventListener(DataEvent.DATA, onData);socket.connect(‘192.168.1.100’, 5000); // ポリシーファイルサーバがないとセキュリティエラーvar parameter:String = …パラメータ生成… ;socket.send(‘GET ’ + parameter + ‘ HTTP/1.0\n\n’); // リクエストヘッダを生成

}

YAPC::Asia 2010

Page 52: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」実装その 2 : Tatsumaki で XMLSocket を書き込むXMLSocket の特徴

ヘッダ : HTTP ではない。 HTTP で送っても Flash 側で自動解析してくれない

ボディ : 何を送っても OK 。 HTTPヘッダをつけて、自前で解析するのもあり

バウンダリ : ヌルバイト” \0”

実装例WebService::Async::Google::TranslateV1_0 にサンプルがあるText::MicroTemplate でレスポンスを XMLSocket形式に変換しているこれを stream_write すれば OK

?= Text::MicroTemplate::encoded_string ‘<?xml version=“1.0” encoding=“UTF-8”?>’

? My ($id, $lang, $message) = @_<result id=“<?= $id ?>”> <translated lang=“<?= $lang ?>”><?= $message ?></translated></result>?= “\0“ ← ここ !! YAPC::Asia 2010

Page 53: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」実装その 3 : Flex でレスポンスを解析する -Connect.as- private function onData(event:DataEvent):void { var data:String = event.data; if (!(/^\s*<[?]xml/).test(data)) { var headerResult:Object = (/^(.*)<[?]xml/xms).exec(data); var header:String = headerResult[1]; var statusResult:Object = (/HTTP\/\d[.]\d[ ](\d{3})[

]\w+/xms).exec(header); var statusCode:String = statusResult[1]; if (statusCode !== '200') { // 例外 } var bodyResult:Object = (/(<[?]xml.*)/xms).exec(data); var body:String = bodyResult[1]; data = body; } var resultXML:XML = new XML(data); // コールバック処理 // 例 . callback(resultXML); }

YAPC::Asia 2010

正規表現でパースするだけ

Page 54: 非同期処理の通知処理 with Tatsumaki

「ぶつぶつの森」実装その 4 : Flex でローディングを表示するプログレスバー

<mx:ProgressBarxmlns:mx=http://www.adobe.com/2006/mxmlid=“progress”indeterminate="false"label=“貸出表を生成中 : %3%%"mode="polled"/>

接続クラスget bytesLoaded と get bytesTotal を実装しておく-Connect.as-public function get bytesLoaded():Number {

return _finishedRequests; // 完了したリクエスト数}public function get bytesTotal():Number {

return _numOfRequests; // 全リクエスト数}

メインクラスvar con:Connect = new Connect();progress.source = con;

YAPC::Asia 2010

タスクの量が分かっていれば、 interminate=“false”

mode=“polled “ で、 bytesLoaded/bytesTotal メソッドを見て自動で進捗表示してくれる

Page 55: 非同期処理の通知処理 with Tatsumaki

まとめ

クライアントから無茶ぶりされても

PSGI/Plack Streaming+

Tatsumaki + gearman + WebService::Async

で大丈夫!Let’s Challenge!!!

YAPC::Asia 2010

Page 56: 非同期処理の通知処理 with Tatsumaki

ご清聴ありがとうございました

YAPC::Asia 2010