SlideShare une entreprise Scribd logo
1  sur  56
非同期処理の通知処理with Tatsumaki 笑いあり、涙あり、萌えあり、ポロリありの20分間 SlideShare版ではうまく変換できないため、20枚に及ぶネタ的部分をカットしました。微妙に意味が通らない記述があったらごめんなさい。 全体の雰囲気はいずれ公式に公開されるであろう動画版でお楽しみください。 keroyonn / Hokkaido.pm YAPC::Asia 2010
自己紹介 WEB系プログラマ in Sapporo 仕事のメイン:Flex/ExtJS 趣味のメイン:C# + WPF サーバーサイド: Perl ちょっとずつ違う素敵なID達 cpan	: keroyonhttp://search.cpan.org/~keroyon/ github	: keroyonnhttp://github.com/keroyonn twitter	: keroyonn_http://twitter.com/keroyonn_ hatena	:keroyon0630http://d.hatena.ne.jp/keroyon0630 YAPC::Asia 2010
今日のお題 WEB越しにガンガン登録される重いタスクを リアルタイムに 待ち時間を感じさせずに リソースを食い潰されないように 処理するにはどうすればいいか 対象者: 非同期サーバーとか触ったことない方 (Plack自体を触ったことなくてもギリギリOK) 自分はバリバリだけど、初心者にうまいこと説明するの難しいなと思ってる方 ポロリを見たい方 YAPC::Asia 2010
第1話: みんな大好きCGI クライアントの非同期化にチャレンジする YAPC::Asia 2010
ブロッキング && ストリーミング with CGI YAPC::Asia 2010 ■その1   ブロッキング、ストリーミングとは      その2Hello World ストリーミング with CGI      その3   クライアント側を非同期化してみる
その1:ブロッキング、ストリーミングとは  クライアント ブロッキング   : Ajax とか使わないので、UIがブロックされる (同期処理)       ボタンが反応しなくて全部固まるよ! ストリーミング : 何らかのフォーマットでデータを読み取り続ける(切断しない) Flash -> XMLSocket Ajax -> multipart/mixed サーバー ブロッキング   : プロセス数 = 同時接続数 (preforkなら) (同期処理)  サーバープロセスひとつで同時に1リクエストまでしかさばけないよ ストリーミング : データを流し続ける or 特定のイベントでデータを流す YAPC::Asia 2010
ブロッキング && ストリーミング with CGI YAPC::Asia 2010      その1   ブロッキング、ストリーミングとは ■その2Hello World ストリーミング with CGI      その3   クライアント側を非同期化してみる
その2 : HelloWorldストリーミング                 with CGI ソースコード #!perl $| = 1; print "Content-Type: text/html"; for (1..5) { 	print "<h2>Hello world!</h2>"; 	sleep 1; } 結果 	hello 	hello 	…5回繰り返す… YAPC::Asia 2010
ブロッキング && ストリーミング with CGI YAPC::Asia 2010      その1   ブロッキング、ストリーミングとは      その2Hello World ストリーミング with CGI ■その3   クライアント側を非同期化してみる
一つ目: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
結果 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
HelloWorldストリーミング with CGI (long-polling 対応版) #!perl open 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"; print "<h2>Hello World '$count'</h2>"; YAPC::Asia 2010
結果 クライアント毎に見える数値が全て変る。 対策 クライアント毎に固有のFIFOなキューを作って、「Hello」を書き込む。 そして接続される度にFlushする。 ※ Tatsumaki::MessageQueueを使うと各クライアント毎に、      次の接続時に出力すべきメッセージ」を管理してくれる Aさんは次Hello5、Bさんは次Hello3とか。 毎回接続が切れているけれど、ストリーミングと言えるのか。 ほぼリアルタイムに取得できるが再接続の時間でやや遅くなる -> 更新頻度が少なければ分からない YAPC::Asia 2010
二つ目:multipart/mixed バウンダリで複数のContent-Type を区切った専用フォーマットでレスポンスを返す 実際のフォーマット例 HTTP/1.0 200 OK Content-Type: multipart/mixed; boundary="ABC" --ABC Content-Type: text/html <h2>Hello World '1'</h2> --ABC Content-Type: text/html <h2>Hello World '2'</h2> --ABC Content-Type: text/html <h2>Hello World '3'</h2> --ABC YAPC::Asia 2010
サーバー側を multipart/mixed 対応にしてみる #!perl $|=1; print qq{Content-Type: multipart/mixed; boundary="ABC"}; print "--ABC"; for (1..5) { 	print "Content-Type: text/html"; 	print "<h2>Hello World '$_'</h2>"; 	print "--ABC"; 	sleep 1; } YAPC::Asia 2010
クライアント側を 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
結論 ストリーミングは C10K 問題の影響を受けすぎる アクセス数と頻度 :10分間で1,000アクセスあり、平均して散らばっている プロセスサイズ   : 30メガとする 通常 処理時間		: 1リクエスト/秒 プロセス数	: 16 メモリ使用量 	: 16プロセス × 30M = 480M ストリーミング 処理時間		: 1リクエスト/10分 プロセス数	: 1,000(60倍以上) メモリ使用量 	: 1,000プロセス × 30M = 3テラ! YAPC::Asia 2010
第2話:「PSGI/Plack ストリーミング」 サーバーの非同期化にチャレンジする YAPC::Asia 2010
ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010 ■その1   ノンブロッキング、ストリーミングとは      その2PSGI/Plack のおさらい      その3 PSGI/Plack ストリーミング仕様
ノンブロッキング && ストリーミング with PSGI/Plack サーバー ノンブロッキング  1プロセス && 1スレッドで、同時接続数いくらでも = 省メモリ ストリーミング   :     データを流し続ける or    特定のイベントでデータを流す YAPC::Asia 2010
ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010      その1   ノンブロッキング、ストリーミングとは ■その2PSGI/Plack のおさらい      その3 PSGI/Plack ストリーミング仕様
Plack おさらい その1:Hello PSGI/Plack ソースコード(簡略版) これじゃ、何だか分からない hello.psgi sub { 	[ 		200, 		['Content-Type' => 'text/html'], 		['<h2>Hello world Plack!</h2>'], 	] } ソースコード(冗長版) でも、これ以上分かりやすくもならない hello.psgi my $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
Plack おさらい その2:Plack のクラス関係 YAPC::Asia 2010
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
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: $v1512"; }); unshift @lines, "HTTP/1.0 $res->[0] @{[ HTTP::Status::status_message($res->[0]) ]}1512“ $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
ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010      その1   ノンブロッキング、ストリーミングとは      その2PSGI/Plack のおさらい ■その3 PSGI/Plack ストリーミング仕様
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"); my $timer;$timer = AE::timer 0, 0.5, sub {# 0.5 秒おきに HelloWorld $writer->write("Content-Type: text/html"); $writer->write(“<h2>Hello World ‘${counter}’</h2>”);#Twiggy::Writer->write を実行 $writer->write("--ABC"); 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
PSGI/Plackストリーミングのクラス関係 YAPC::Asia 2010
Twiggy のコード解説 #1 1. _create_tcp_servertcpサーバーを生成 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
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は非同期しか使ってない
大量のアクセスを捌くと言われる非道鬼天皇: その強さの秘密とは? 3つのひみつ 究極の受け流し性能 :  自分では何もせず、右から左へ受け流す 強力な3つの下僕達 :            あらゆる処理を代りにこなす忠実な下僕 萌え萌えな容姿 :            キャラクターグッズが出るくらいの萌え YAPC::Asia 2010
大量のアクセスを捌くと言われる非道鬼天皇: その弱点とは? 3つの弱点 同期IO処理  : 一瞬でもブロックしてはいけない。 毎回非同期IO処理を手で書くの? CPU使う処理 : CPU処理でブロックされる場合ももちろんダメ。                          全部自分でforkするの?                                           そもそも重い処理は別サーバーにしたいけど。。。 書きづらい: 低レベルの処理が剥き出しになっている。 いちいちディスパッチするコード書くの? multipart/mixed をいちいち手で出力させるの? YAPC::Asia 2010
第3話:三つの下僕に命令だ !! モジュールを利用して非同期プログラミングを実用的に行う YAPC::Asia 2010
三つの下僕 その1: Tatsumakiその特徴 「書きづらい」という欠点を補う   非同期WEBアプリケーションフレームワーク ディスパッチ処理 multipart/mixed の自動生成 staticファイル配信 メッセージキュー YAPC::Asia 2010
三つの下僕 その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
三つの下僕 その1: Tatsumakiまとめ Tatsumaki::Application 複数のハンドラをディスパッチ static_path メソッドで静的コンテンツ配信 Tatsumaki::Handler リクエストメソッドに対応して get/post 関数を実行してくれる multipart/mixed を自動で作成してくれる リファレンスを書き出すと、自動でJSONにエンコードしてくれる Tatsumaki::MessageQueue 各クライアント毎に、「次の接続時に出力すべきメッセージ」を管理してくれる ※今回は説明しません。 YAPC::Asia 2010
三つの下僕 その2: Gearmanその特徴 重いCPU処理が苦手という欠点を補うジョブキューサーバー 登録しておいて後でゆっくりではなく、即時に処理してくれる 処理の進捗状態をクライアントに伝えることができる ワーカー: サーバーにジョブ名を登録する。 タスク(コードリファレンス)をジョブ名に紐付けておく サーバーからジョブと引数を取って来て、実行する サーバー  : クライアントから依頼された仕事をキューに溜めておく ワーカーから問合せがあったら、キューからジョブと引数を取ってきて渡す クライアント : サーバーにジョブ名と引数を渡して処理を依頼する YAPC::Asia 2010 ジョブ 取得 ジョブ 登録 結果 ジョブ 依頼 結果
三つの下僕 その2: Gearmanデモ 1. ワーカーから CAN_DO(1) パケットを送って、HELLO というジョブを処理できるよ! とサーバーに教える perl -e 'print "REQ" . pack("NN", 1, 5) . "HELLO"' | nc 0 4730         status :HELLO   0       0       1 2. クライアントから SUBMIT_JOB(7) パケットを送って、HELLO(xxx) というジョブを処理してね! とサーバーに伝えて、         キューイングしてもらう perl -e 'print "REQ" . pack("NN", 7, 10) . "HELLOxxx"' | nc 0 4730         status :   HELLO   1       0       1 3. ワーカーから GRAB_JOB(9) 自分の処理できるジョブを頂戴! とサーバーに伝えて、         ジョブ名 HELLO と、引数 xxx をサーバーから受け取る perl -e 'print "REQ" . pack("NN", 9, 5) . "HELLO"' | nc 0 4730         ※CAN_DOをしたのと別接続になっちゃっているので、NO_JOB というのが返ってくる YAPC::Asia 2010
三つの下僕 その2: GearmanHello World with Gearman ジョブを定義する ジョブクラス: MyJobs.pm      hello を秒間1回、計5回出力する。job_* で始まる単なる関数 package MyJobs;      subjob_hello {          my $job = shift; my $arg = $job->arg; # クライアントが設定した引数 for (1..5) {              print "hello $arg $_";              sleep 1; $job->set_status($_, 5); # 進捗ステータスを更新 } return “finished:$arg”; # 完了したらリターン      }      1; YAPC::Asia 2010
三つの下僕 その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@‘	=> y $servers,          'prefix=s‘	=> y $prefix,          'max-workers=i‘	=> max_workers,          'h|help‘	=> y $help,      ) or pod2usage();      pod2usage() unless $servers && @$servers;      pod2usage() if $help; YAPC::Asia 2010
三つの下僕 その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
三つの下僕 その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
三つの下僕 その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";                  $cv->end;              }, on_status => sub {	# ジョブの進捗状況を受け取ることができる                  my ($self, $n, $d) = @_;                  print "status => $n:$d";              }, ); } $cv->recv; 		# タスクの終了を待ち受ける必要がある YAPC::Asia 2010
三つの下僕 その2: Gearmanデモ ジョブを10個投入してみる この HelloWorldGearman は1ジョブで5秒かかる 従って ワーカーの並列数を10個にすると、5秒かかる ワーカーの並列数を5個にすると、10秒かかる YAPC::Asia 2010
三つの下僕 その2: Gearmanまとめ ワーカー 手動でたくさん立ち上げるのは管理が面倒なので、             スタートアップ用のスクリプトを作っておくとよい register_functionでジョブを登録し、コードリファレンスと紐付ける workでジョブをグラブして実行する クライアント クライアント側(Tatsumaki)は、非同期とした方がよいため、 AnyEvent::Gearmanを使う。ワーカー側はその限りではない add_taskで、ジョブをキューイングし、ハンドラで終了を待ち受ける AnyEventのループを回す必要 YAPC::Asia 2010
三つの下僕 その3: WebService::Asyncその特徴  非同期IO処理を毎回手で書かなくちゃいけないという 欠点を補うためのモジュール                 ※ 三つないと“ごろ”が悪いので作った。 じゃないと、三つの下僕にならないじゃん。 Tatsumaki::HTTPClient にはない各種の機能 非同期であるにも関わらず、WebService::Simple ばりに簡単 キャッシング レスポンスのパース レスポンスの変換(Text::MicroTemplate でXMLにするとか) リトライ処理 詳細なロギング 結果はコールバックとしても、戻り値としても処理可能 複数のリクエストとレスポンスの対応付けを柔軟に処理可能 柔軟にカスタマイズ可能 YAPC::Asia 2010
三つの下僕 その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}";          },      ); $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
最終話: Flex でサービスを構築するには 例. 「ぶつぶつの森 ~ ぶつぶつ交換で始まる愛もある」 リアル知人をターゲットにした  所有メディア登録->飲み会予約->ぶつぶつ交換システム YAPC::Asia 2010
「ぶつぶつの森」:   要件など 要件(抜粋) 会員の所有物(主に書籍、音楽などのメディア)をカタログ化してPC、携帯、印刷物上で見られるようにする。 デジタル書籍/音楽に関してはファイル名一覧を送ると、Amazon、楽天、Wikipedia などを マッシュアップしてカタログ化する ユーザー登録時に一括で所有書籍を登録してもらう 処理中には処理が完了したものから随時一覧を表示し、クリックで詳細に飛べるようにする 処理が完了すると、zip 圧縮されたカタログ(PDFなど)がダウンロード可能となる 設計 複数ファイルを選択してファイル名を送信する必要がある -> Flash(Flex/Air) 処理結果をリアルタイムでクライアントに送る必要がある -> XMLSocketによるストリーミング (例えば)HTMLを生成してZIP圧縮する必要があるためCPUを消費する -> Gearman による分散処理 実装のポイント ソケットポリシーファイルのサーバが必要 Flex側はHTTPのヘッダを自前で生成/解析する Tatsumaki側は、XMLSocketを作ってはくれない。自前でXMLにしてヌルバイトを付加する必要。 Gearman のキューイングされている待ちタスクの数を取得する ワーカー側での set_statusによるローディングの表現 YAPC::Asia 2010
「ぶつぶつの森」:   全体の仕組み YAPC::Asia 2010 Twiggy/Tatsumaki Gearmanクライアント [AnyEvent::Gearman] Gearman サーバー Flash(Flex/Air) ・ファイル一覧取得 (FileReferenceList) ・ヘッダ生成 ・レスポンス解析 ・ローディング表示 on_complete on_data on_status DB 1 2 3 4 5 6 7 ファイ ル選択 一覧表 処理1 処理2 待ち1 待ち2 処理3 待ち3 Gearmanワーカー [Gearman::Worker] set_status return 処理中 ローディングを提供 処理待ち(キュー) 待ちの数を提供 1 2 3
「ぶつぶつの森」実装その1:   サーバ設置とリクエスト生成(Flex) ソケットポリシーファイルサーバの設置 $ cpanm AnyEvent::FlashSocketPolicy      $ su -       % flash-socket-policy-server --domain-policy=master-only br />--domain='*' br />--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’);// リクエストヘッダを生成 } YAPC::Asia 2010
「ぶつぶつの森」実装その2:TatsumakiでXMLSocketを書き込む XMLSocketの特徴 ヘッダ:  HTTPではない。HTTPで送ってもFlash側で自動解析してくれない ボディ: 何を送ってもOK。HTTPヘッダをつけて、自前で解析するのもあり バウンダリ:  ヌルバイト”” 実装例 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> ?= ““       ← ここ!! YAPC::Asia 2010
「ぶつぶつの森」実装その3:Flexでレスポンスを解析する -Connect.as-  private function onData(event:DataEvent):void {          var data:String = event.data;          if (!(/^*<[?]xml/).test(data)) {              var headerResult:Object = (/^(.*)<[?]xml/xms).exec(data);              var header:String = headerResult[1];              var statusResult:Object = (/HTTP[.][ ]({3})[ ]+/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 正規表現で パースするだけ
「ぶつぶつの森」実装その4:Flexでローディングを表示する プログレスバー <mx:ProgressBar xmlns:mx=http://www.adobe.com/2006/mxml id=“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 メソッドを見て自動で進捗表示してくれる
まとめ クライアントから無茶ぶりされても PSGI/Plack Streaming + Tatsumaki + gearman + WebService::Async で大丈夫! Let’s Challenge!!! YAPC::Asia 2010
ご清聴ありがとうございました YAPC::Asia 2010

Contenu connexe

Tendances

Php 7 hhvm and co
Php 7 hhvm and coPhp 7 hhvm and co
Php 7 hhvm and coPierre Joye
 
Introduction to Guzzle
Introduction to GuzzleIntroduction to Guzzle
Introduction to GuzzleDQNEO
 
PerlでWeb API入門
PerlでWeb API入門PerlでWeb API入門
PerlでWeb API入門Yusuke Wada
 
FizzBuzzではじめるテスト
FizzBuzzではじめるテストFizzBuzzではじめるテスト
FizzBuzzではじめるテストMasashi Shinbara
 
07 Introduction to PHP #burningkeyboards
07 Introduction to PHP #burningkeyboards07 Introduction to PHP #burningkeyboards
07 Introduction to PHP #burningkeyboardsDenis Ristic
 
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceBeijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceJesse Vincent
 
Quick tour of PHP from inside
Quick tour of PHP from insideQuick tour of PHP from inside
Quick tour of PHP from insidejulien pauli
 
Php Tutorial | Introduction Demo | Basics
 Php Tutorial | Introduction Demo | Basics Php Tutorial | Introduction Demo | Basics
Php Tutorial | Introduction Demo | BasicsShubham Kumar Singh
 
Even Faster Web Sites at The Ajax Experience
Even Faster Web Sites at The Ajax ExperienceEven Faster Web Sites at The Ajax Experience
Even Faster Web Sites at The Ajax ExperienceSteve Souders
 
Make Your Own Perl with Moops
Make Your Own Perl with MoopsMake Your Own Perl with Moops
Make Your Own Perl with MoopsMike Friedman
 
Php Calling Operators
Php Calling OperatorsPhp Calling Operators
Php Calling Operatorsmussawir20
 
What RabbitMQ can do for you (phpnw14 Uncon)
What RabbitMQ can do for you (phpnw14 Uncon)What RabbitMQ can do for you (phpnw14 Uncon)
What RabbitMQ can do for you (phpnw14 Uncon)James Titcumb
 

Tendances (19)

Php 7 hhvm and co
Php 7 hhvm and coPhp 7 hhvm and co
Php 7 hhvm and co
 
Hardcore PHP
Hardcore PHPHardcore PHP
Hardcore PHP
 
Introduction to Guzzle
Introduction to GuzzleIntroduction to Guzzle
Introduction to Guzzle
 
PerlでWeb API入門
PerlでWeb API入門PerlでWeb API入門
PerlでWeb API入門
 
Intermediate PHP
Intermediate PHPIntermediate PHP
Intermediate PHP
 
FizzBuzzではじめるテスト
FizzBuzzではじめるテストFizzBuzzではじめるテスト
FizzBuzzではじめるテスト
 
07 Introduction to PHP #burningkeyboards
07 Introduction to PHP #burningkeyboards07 Introduction to PHP #burningkeyboards
07 Introduction to PHP #burningkeyboards
 
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceBeijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
 
Quick tour of PHP from inside
Quick tour of PHP from insideQuick tour of PHP from inside
Quick tour of PHP from inside
 
Php Tutorial | Introduction Demo | Basics
 Php Tutorial | Introduction Demo | Basics Php Tutorial | Introduction Demo | Basics
Php Tutorial | Introduction Demo | Basics
 
Perl Moderno
Perl ModernoPerl Moderno
Perl Moderno
 
Even Faster Web Sites at The Ajax Experience
Even Faster Web Sites at The Ajax ExperienceEven Faster Web Sites at The Ajax Experience
Even Faster Web Sites at The Ajax Experience
 
Introduction to PHP
Introduction to PHPIntroduction to PHP
Introduction to PHP
 
Make Your Own Perl with Moops
Make Your Own Perl with MoopsMake Your Own Perl with Moops
Make Your Own Perl with Moops
 
Php Calling Operators
Php Calling OperatorsPhp Calling Operators
Php Calling Operators
 
RESTful web services
RESTful web servicesRESTful web services
RESTful web services
 
Php mysql
Php mysqlPhp mysql
Php mysql
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
What RabbitMQ can do for you (phpnw14 Uncon)
What RabbitMQ can do for you (phpnw14 Uncon)What RabbitMQ can do for you (phpnw14 Uncon)
What RabbitMQ can do for you (phpnw14 Uncon)
 

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

Implementing Comet using PHP
Implementing Comet using PHPImplementing Comet using PHP
Implementing Comet using PHPKing Foo
 
Php Crash Course
Php Crash CoursePhp Crash Course
Php Crash Coursemussawir20
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpmsom_nangia
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpmwilburlo
 
Google在Web前端方面的经验
Google在Web前端方面的经验Google在Web前端方面的经验
Google在Web前端方面的经验yiditushe
 
SXSW: Even Faster Web Sites
SXSW: Even Faster Web SitesSXSW: Even Faster Web Sites
SXSW: Even Faster Web SitesSteve Souders
 
Even Faster Web Sites at jQuery Conference '09
Even Faster Web Sites at jQuery Conference '09Even Faster Web Sites at jQuery Conference '09
Even Faster Web Sites at jQuery Conference '09Steve Souders
 
Enterprise AIR Development for JavaScript Developers
Enterprise AIR Development for JavaScript DevelopersEnterprise AIR Development for JavaScript Developers
Enterprise AIR Development for JavaScript DevelopersAndreCharland
 
How Xslate Works
How Xslate WorksHow Xslate Works
How Xslate WorksGoro Fuji
 
Exploiting the newer perl to improve your plugins
Exploiting the newer perl to improve your pluginsExploiting the newer perl to improve your plugins
Exploiting the newer perl to improve your pluginsMarian Marinov
 

Similaire à 非同期処理の通知処理 with Tatsumaki (20)

Web::Scraper
Web::ScraperWeb::Scraper
Web::Scraper
 
Writing Pluggable Software
Writing Pluggable SoftwareWriting Pluggable Software
Writing Pluggable Software
 
Modern Perl
Modern PerlModern Perl
Modern Perl
 
&lt;img src="xss.com">
&lt;img src="xss.com">&lt;img src="xss.com">
&lt;img src="xss.com">
 
Fav
FavFav
Fav
 
Implementing Comet using PHP
Implementing Comet using PHPImplementing Comet using PHP
Implementing Comet using PHP
 
Php Crash Course
Php Crash CoursePhp Crash Course
Php Crash Course
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpm
 
Psgi Plack Sfpm
Psgi Plack SfpmPsgi Plack Sfpm
Psgi Plack Sfpm
 
Google在Web前端方面的经验
Google在Web前端方面的经验Google在Web前端方面的经验
Google在Web前端方面的经验
 
Sxsw 20090314
Sxsw 20090314Sxsw 20090314
Sxsw 20090314
 
SXSW: Even Faster Web Sites
SXSW: Even Faster Web SitesSXSW: Even Faster Web Sites
SXSW: Even Faster Web Sites
 
Even Faster Web Sites at jQuery Conference '09
Even Faster Web Sites at jQuery Conference '09Even Faster Web Sites at jQuery Conference '09
Even Faster Web Sites at jQuery Conference '09
 
Enterprise AIR Development for JavaScript Developers
Enterprise AIR Development for JavaScript DevelopersEnterprise AIR Development for JavaScript Developers
Enterprise AIR Development for JavaScript Developers
 
Php mysql ppt
Php mysql pptPhp mysql ppt
Php mysql ppt
 
Plack at YAPC::NA 2010
Plack at YAPC::NA 2010Plack at YAPC::NA 2010
Plack at YAPC::NA 2010
 
Plack - LPW 2009
Plack - LPW 2009Plack - LPW 2009
Plack - LPW 2009
 
How Xslate Works
How Xslate WorksHow Xslate Works
How Xslate Works
 
Exploiting the newer perl to improve your plugins
Exploiting the newer perl to improve your pluginsExploiting the newer perl to improve your plugins
Exploiting the newer perl to improve your plugins
 
Mojolicious on Steroids
Mojolicious on SteroidsMojolicious on Steroids
Mojolicious on Steroids
 

Plus de keroyonn

YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイド
YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイドYAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイド
YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイドkeroyonn
 
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうPerl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうkeroyonn
 
Perlで次世代ゲーム開発
Perlで次世代ゲーム開発Perlで次世代ゲーム開発
Perlで次世代ゲーム開発keroyonn
 
実用裏方 Perl 入門
実用裏方 Perl 入門実用裏方 Perl 入門
実用裏方 Perl 入門keroyonn
 
Perl/CGI 入門
Perl/CGI 入門Perl/CGI 入門
Perl/CGI 入門keroyonn
 
Perl logging
Perl loggingPerl logging
Perl loggingkeroyonn
 

Plus de keroyonn (6)

YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイド
YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイドYAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイド
YAPC::Hokkaido 2016 「普段使い言語環境」更新によるスキルリセットサバイバルガイド
 
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうPerl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
 
Perlで次世代ゲーム開発
Perlで次世代ゲーム開発Perlで次世代ゲーム開発
Perlで次世代ゲーム開発
 
実用裏方 Perl 入門
実用裏方 Perl 入門実用裏方 Perl 入門
実用裏方 Perl 入門
 
Perl/CGI 入門
Perl/CGI 入門Perl/CGI 入門
Perl/CGI 入門
 
Perl logging
Perl loggingPerl logging
Perl logging
 

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

  • 1. 非同期処理の通知処理with Tatsumaki 笑いあり、涙あり、萌えあり、ポロリありの20分間 SlideShare版ではうまく変換できないため、20枚に及ぶネタ的部分をカットしました。微妙に意味が通らない記述があったらごめんなさい。 全体の雰囲気はいずれ公式に公開されるであろう動画版でお楽しみください。 keroyonn / Hokkaido.pm YAPC::Asia 2010
  • 2. 自己紹介 WEB系プログラマ in Sapporo 仕事のメイン:Flex/ExtJS 趣味のメイン:C# + WPF サーバーサイド: Perl ちょっとずつ違う素敵なID達 cpan : keroyonhttp://search.cpan.org/~keroyon/ github : keroyonnhttp://github.com/keroyonn twitter : keroyonn_http://twitter.com/keroyonn_ hatena :keroyon0630http://d.hatena.ne.jp/keroyon0630 YAPC::Asia 2010
  • 3. 今日のお題 WEB越しにガンガン登録される重いタスクを リアルタイムに 待ち時間を感じさせずに リソースを食い潰されないように 処理するにはどうすればいいか 対象者: 非同期サーバーとか触ったことない方 (Plack自体を触ったことなくてもギリギリOK) 自分はバリバリだけど、初心者にうまいこと説明するの難しいなと思ってる方 ポロリを見たい方 YAPC::Asia 2010
  • 5. ブロッキング && ストリーミング with CGI YAPC::Asia 2010 ■その1 ブロッキング、ストリーミングとは その2Hello World ストリーミング with CGI その3 クライアント側を非同期化してみる
  • 6. その1:ブロッキング、ストリーミングとは クライアント ブロッキング : Ajax とか使わないので、UIがブロックされる (同期処理) ボタンが反応しなくて全部固まるよ! ストリーミング : 何らかのフォーマットでデータを読み取り続ける(切断しない) Flash -> XMLSocket Ajax -> multipart/mixed サーバー ブロッキング : プロセス数 = 同時接続数 (preforkなら) (同期処理) サーバープロセスひとつで同時に1リクエストまでしかさばけないよ ストリーミング : データを流し続ける or 特定のイベントでデータを流す YAPC::Asia 2010
  • 7. ブロッキング && ストリーミング with CGI YAPC::Asia 2010 その1 ブロッキング、ストリーミングとは ■その2Hello World ストリーミング with CGI その3 クライアント側を非同期化してみる
  • 8. その2 : HelloWorldストリーミング with CGI ソースコード #!perl $| = 1; print "Content-Type: text/html"; for (1..5) { print "<h2>Hello world!</h2>"; sleep 1; } 結果 hello hello …5回繰り返す… YAPC::Asia 2010
  • 9. ブロッキング && ストリーミング with CGI YAPC::Asia 2010 その1 ブロッキング、ストリーミングとは その2Hello World ストリーミング with CGI ■その3 クライアント側を非同期化してみる
  • 10. 一つ目: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
  • 11. 結果 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
  • 12. HelloWorldストリーミング with CGI (long-polling 対応版) #!perl open 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"; print "<h2>Hello World '$count'</h2>"; YAPC::Asia 2010
  • 13. 結果 クライアント毎に見える数値が全て変る。 対策 クライアント毎に固有のFIFOなキューを作って、「Hello」を書き込む。 そして接続される度にFlushする。 ※ Tatsumaki::MessageQueueを使うと各クライアント毎に、 次の接続時に出力すべきメッセージ」を管理してくれる Aさんは次Hello5、Bさんは次Hello3とか。 毎回接続が切れているけれど、ストリーミングと言えるのか。 ほぼリアルタイムに取得できるが再接続の時間でやや遅くなる -> 更新頻度が少なければ分からない YAPC::Asia 2010
  • 14. 二つ目:multipart/mixed バウンダリで複数のContent-Type を区切った専用フォーマットでレスポンスを返す 実際のフォーマット例 HTTP/1.0 200 OK Content-Type: multipart/mixed; boundary="ABC" --ABC Content-Type: text/html <h2>Hello World '1'</h2> --ABC Content-Type: text/html <h2>Hello World '2'</h2> --ABC Content-Type: text/html <h2>Hello World '3'</h2> --ABC YAPC::Asia 2010
  • 15. サーバー側を multipart/mixed 対応にしてみる #!perl $|=1; print qq{Content-Type: multipart/mixed; boundary="ABC"}; print "--ABC"; for (1..5) { print "Content-Type: text/html"; print "<h2>Hello World '$_'</h2>"; print "--ABC"; sleep 1; } YAPC::Asia 2010
  • 16. クライアント側を 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
  • 17. 結論 ストリーミングは C10K 問題の影響を受けすぎる アクセス数と頻度 :10分間で1,000アクセスあり、平均して散らばっている プロセスサイズ : 30メガとする 通常 処理時間 : 1リクエスト/秒 プロセス数 : 16 メモリ使用量 : 16プロセス × 30M = 480M ストリーミング 処理時間 : 1リクエスト/10分 プロセス数 : 1,000(60倍以上) メモリ使用量 : 1,000プロセス × 30M = 3テラ! YAPC::Asia 2010
  • 19. ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010 ■その1 ノンブロッキング、ストリーミングとは その2PSGI/Plack のおさらい その3 PSGI/Plack ストリーミング仕様
  • 20. ノンブロッキング && ストリーミング with PSGI/Plack サーバー ノンブロッキング 1プロセス && 1スレッドで、同時接続数いくらでも = 省メモリ ストリーミング : データを流し続ける or 特定のイベントでデータを流す YAPC::Asia 2010
  • 21. ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010 その1 ノンブロッキング、ストリーミングとは ■その2PSGI/Plack のおさらい その3 PSGI/Plack ストリーミング仕様
  • 22. Plack おさらい その1:Hello PSGI/Plack ソースコード(簡略版) これじゃ、何だか分からない hello.psgi sub { [ 200, ['Content-Type' => 'text/html'], ['<h2>Hello world Plack!</h2>'], ] } ソースコード(冗長版) でも、これ以上分かりやすくもならない hello.psgi my $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
  • 23. Plack おさらい その2:Plack のクラス関係 YAPC::Asia 2010
  • 24. 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
  • 25. 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: $v1512"; }); unshift @lines, "HTTP/1.0 $res->[0] @{[ HTTP::Status::status_message($res->[0]) ]}1512“ $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
  • 26. ノンブロッキング && ストリーミングwith PSGI/Plack YAPC::Asia 2010 その1 ノンブロッキング、ストリーミングとは その2PSGI/Plack のおさらい ■その3 PSGI/Plack ストリーミング仕様
  • 27. 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"); my $timer;$timer = AE::timer 0, 0.5, sub {# 0.5 秒おきに HelloWorld $writer->write("Content-Type: text/html"); $writer->write(“<h2>Hello World ‘${counter}’</h2>”);#Twiggy::Writer->write を実行 $writer->write("--ABC"); 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
  • 29. Twiggy のコード解説 #1 1. _create_tcp_servertcpサーバーを生成 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
  • 30. 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は非同期しか使ってない
  • 31. 大量のアクセスを捌くと言われる非道鬼天皇: その強さの秘密とは? 3つのひみつ 究極の受け流し性能 : 自分では何もせず、右から左へ受け流す 強力な3つの下僕達 : あらゆる処理を代りにこなす忠実な下僕 萌え萌えな容姿 : キャラクターグッズが出るくらいの萌え YAPC::Asia 2010
  • 32. 大量のアクセスを捌くと言われる非道鬼天皇: その弱点とは? 3つの弱点 同期IO処理 : 一瞬でもブロックしてはいけない。 毎回非同期IO処理を手で書くの? CPU使う処理 : CPU処理でブロックされる場合ももちろんダメ。 全部自分でforkするの? そもそも重い処理は別サーバーにしたいけど。。。 書きづらい: 低レベルの処理が剥き出しになっている。 いちいちディスパッチするコード書くの? multipart/mixed をいちいち手で出力させるの? YAPC::Asia 2010
  • 34. 三つの下僕 その1: Tatsumakiその特徴 「書きづらい」という欠点を補う 非同期WEBアプリケーションフレームワーク ディスパッチ処理 multipart/mixed の自動生成 staticファイル配信 メッセージキュー YAPC::Asia 2010
  • 35. 三つの下僕 その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
  • 36. 三つの下僕 その1: Tatsumakiまとめ Tatsumaki::Application 複数のハンドラをディスパッチ static_path メソッドで静的コンテンツ配信 Tatsumaki::Handler リクエストメソッドに対応して get/post 関数を実行してくれる multipart/mixed を自動で作成してくれる リファレンスを書き出すと、自動でJSONにエンコードしてくれる Tatsumaki::MessageQueue 各クライアント毎に、「次の接続時に出力すべきメッセージ」を管理してくれる ※今回は説明しません。 YAPC::Asia 2010
  • 37. 三つの下僕 その2: Gearmanその特徴 重いCPU処理が苦手という欠点を補うジョブキューサーバー 登録しておいて後でゆっくりではなく、即時に処理してくれる 処理の進捗状態をクライアントに伝えることができる ワーカー: サーバーにジョブ名を登録する。 タスク(コードリファレンス)をジョブ名に紐付けておく サーバーからジョブと引数を取って来て、実行する サーバー : クライアントから依頼された仕事をキューに溜めておく ワーカーから問合せがあったら、キューからジョブと引数を取ってきて渡す クライアント : サーバーにジョブ名と引数を渡して処理を依頼する YAPC::Asia 2010 ジョブ 取得 ジョブ 登録 結果 ジョブ 依頼 結果
  • 38. 三つの下僕 その2: Gearmanデモ 1. ワーカーから CAN_DO(1) パケットを送って、HELLO というジョブを処理できるよ! とサーバーに教える perl -e 'print "REQ" . pack("NN", 1, 5) . "HELLO"' | nc 0 4730 status :HELLO 0 0 1 2. クライアントから SUBMIT_JOB(7) パケットを送って、HELLO(xxx) というジョブを処理してね! とサーバーに伝えて、 キューイングしてもらう perl -e 'print "REQ" . pack("NN", 7, 10) . "HELLOxxx"' | nc 0 4730 status : HELLO 1 0 1 3. ワーカーから GRAB_JOB(9) 自分の処理できるジョブを頂戴! とサーバーに伝えて、 ジョブ名 HELLO と、引数 xxx をサーバーから受け取る perl -e 'print "REQ" . pack("NN", 9, 5) . "HELLO"' | nc 0 4730 ※CAN_DOをしたのと別接続になっちゃっているので、NO_JOB というのが返ってくる YAPC::Asia 2010
  • 39. 三つの下僕 その2: GearmanHello World with Gearman ジョブを定義する ジョブクラス: MyJobs.pm hello を秒間1回、計5回出力する。job_* で始まる単なる関数 package MyJobs; subjob_hello { my $job = shift; my $arg = $job->arg; # クライアントが設定した引数 for (1..5) { print "hello $arg $_"; sleep 1; $job->set_status($_, 5); # 進捗ステータスを更新 } return “finished:$arg”; # 完了したらリターン } 1; YAPC::Asia 2010
  • 40. 三つの下僕 その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@‘ => y $servers, 'prefix=s‘ => y $prefix, 'max-workers=i‘ => max_workers, 'h|help‘ => y $help, ) or pod2usage(); pod2usage() unless $servers && @$servers; pod2usage() if $help; YAPC::Asia 2010
  • 41. 三つの下僕 その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
  • 42. 三つの下僕 その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
  • 43. 三つの下僕 その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"; $cv->end; }, on_status => sub { # ジョブの進捗状況を受け取ることができる my ($self, $n, $d) = @_; print "status => $n:$d"; }, ); } $cv->recv; # タスクの終了を待ち受ける必要がある YAPC::Asia 2010
  • 44. 三つの下僕 その2: Gearmanデモ ジョブを10個投入してみる この HelloWorldGearman は1ジョブで5秒かかる 従って ワーカーの並列数を10個にすると、5秒かかる ワーカーの並列数を5個にすると、10秒かかる YAPC::Asia 2010
  • 45. 三つの下僕 その2: Gearmanまとめ ワーカー 手動でたくさん立ち上げるのは管理が面倒なので、 スタートアップ用のスクリプトを作っておくとよい register_functionでジョブを登録し、コードリファレンスと紐付ける workでジョブをグラブして実行する クライアント クライアント側(Tatsumaki)は、非同期とした方がよいため、 AnyEvent::Gearmanを使う。ワーカー側はその限りではない add_taskで、ジョブをキューイングし、ハンドラで終了を待ち受ける AnyEventのループを回す必要 YAPC::Asia 2010
  • 46. 三つの下僕 その3: WebService::Asyncその特徴 非同期IO処理を毎回手で書かなくちゃいけないという 欠点を補うためのモジュール ※ 三つないと“ごろ”が悪いので作った。 じゃないと、三つの下僕にならないじゃん。 Tatsumaki::HTTPClient にはない各種の機能 非同期であるにも関わらず、WebService::Simple ばりに簡単 キャッシング レスポンスのパース レスポンスの変換(Text::MicroTemplate でXMLにするとか) リトライ処理 詳細なロギング 結果はコールバックとしても、戻り値としても処理可能 複数のリクエストとレスポンスの対応付けを柔軟に処理可能 柔軟にカスタマイズ可能 YAPC::Asia 2010
  • 47. 三つの下僕 その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}"; }, ); $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
  • 48. 最終話: Flex でサービスを構築するには 例. 「ぶつぶつの森 ~ ぶつぶつ交換で始まる愛もある」 リアル知人をターゲットにした 所有メディア登録->飲み会予約->ぶつぶつ交換システム YAPC::Asia 2010
  • 49. 「ぶつぶつの森」: 要件など 要件(抜粋) 会員の所有物(主に書籍、音楽などのメディア)をカタログ化してPC、携帯、印刷物上で見られるようにする。 デジタル書籍/音楽に関してはファイル名一覧を送ると、Amazon、楽天、Wikipedia などを マッシュアップしてカタログ化する ユーザー登録時に一括で所有書籍を登録してもらう 処理中には処理が完了したものから随時一覧を表示し、クリックで詳細に飛べるようにする 処理が完了すると、zip 圧縮されたカタログ(PDFなど)がダウンロード可能となる 設計 複数ファイルを選択してファイル名を送信する必要がある -> Flash(Flex/Air) 処理結果をリアルタイムでクライアントに送る必要がある -> XMLSocketによるストリーミング (例えば)HTMLを生成してZIP圧縮する必要があるためCPUを消費する -> Gearman による分散処理 実装のポイント ソケットポリシーファイルのサーバが必要 Flex側はHTTPのヘッダを自前で生成/解析する Tatsumaki側は、XMLSocketを作ってはくれない。自前でXMLにしてヌルバイトを付加する必要。 Gearman のキューイングされている待ちタスクの数を取得する ワーカー側での set_statusによるローディングの表現 YAPC::Asia 2010
  • 50. 「ぶつぶつの森」: 全体の仕組み YAPC::Asia 2010 Twiggy/Tatsumaki Gearmanクライアント [AnyEvent::Gearman] Gearman サーバー Flash(Flex/Air) ・ファイル一覧取得 (FileReferenceList) ・ヘッダ生成 ・レスポンス解析 ・ローディング表示 on_complete on_data on_status DB 1 2 3 4 5 6 7 ファイ ル選択 一覧表 処理1 処理2 待ち1 待ち2 処理3 待ち3 Gearmanワーカー [Gearman::Worker] set_status return 処理中 ローディングを提供 処理待ち(キュー) 待ちの数を提供 1 2 3
  • 51. 「ぶつぶつの森」実装その1: サーバ設置とリクエスト生成(Flex) ソケットポリシーファイルサーバの設置 $ cpanm AnyEvent::FlashSocketPolicy $ su - % flash-socket-policy-server --domain-policy=master-only br />--domain='*' br />--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’);// リクエストヘッダを生成 } YAPC::Asia 2010
  • 52. 「ぶつぶつの森」実装その2:TatsumakiでXMLSocketを書き込む XMLSocketの特徴 ヘッダ: HTTPではない。HTTPで送ってもFlash側で自動解析してくれない ボディ: 何を送ってもOK。HTTPヘッダをつけて、自前で解析するのもあり バウンダリ: ヌルバイト”” 実装例 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> ?= ““ ← ここ!! YAPC::Asia 2010
  • 53. 「ぶつぶつの森」実装その3:Flexでレスポンスを解析する -Connect.as- private function onData(event:DataEvent):void { var data:String = event.data; if (!(/^*<[?]xml/).test(data)) { var headerResult:Object = (/^(.*)<[?]xml/xms).exec(data); var header:String = headerResult[1]; var statusResult:Object = (/HTTP[.][ ]({3})[ ]+/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 正規表現で パースするだけ
  • 54. 「ぶつぶつの森」実装その4:Flexでローディングを表示する プログレスバー <mx:ProgressBar xmlns:mx=http://www.adobe.com/2006/mxml id=“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 メソッドを見て自動で進捗表示してくれる
  • 55. まとめ クライアントから無茶ぶりされても PSGI/Plack Streaming + Tatsumaki + gearman + WebService::Async で大丈夫! Let’s Challenge!!! YAPC::Asia 2010