Asial Blog

Recruit! Asialで一緒に働きませんか?

PHP+Kestrel+Supervisorでお手軽タスクキューイング

カテゴリ :
バックエンド(プログラミング)
タグ :
PHP


こんにちは、久保田です。

ウェブサービスでは、ユーザのアクションに従ってバッチ処理を行わなければならないケースがままあります。この記事では、バッチ処理の手法の一つであるタスクキューイングをPHPとKestrelSupervisorを利用して行うやり方の導入を紹介します。

なぜなにタスクキューイング

ウェブサービスでは、ユーザのアクションに従って非同期にバッチ処理を行うようなケースがよくあります。

例えばflickrのような写真を共有するウェブサービスで言えば、ユーザが写真をアップロードしたあとに非同期でその画像の複数のサムネイル生成や加工をしなければならないケースがあります。

よく見られるのは、DBにバッチ処理のためのタスクデータを入れておいて、後でcronで定期的に起動するワーカープロセスからバッチ処理を行う方法です。このやり方には、ワーカーを複数プロセスで扱いづらい、処理がリアルタイムになされない、という欠点があります。

これとは別に、メッセージキューを使ってバッチ処理をユーザのアクションとは非同期に行うやり方があります。ウェブサービスのフロントエンド側では、メッセージキューにタスクに関するデータを投げ込んでおいて、実際のバッチ処理などはバックエンドに常に控えているワーカープロセスがリアルタイムにバッチ処理を行います。

以下では、メッセージキューサーバであるKestrelとPHPとSupervisorを使うお手軽タスクキューイングの導入を紹介します。

Kestrelとは

Kestrelは、twitterのバックエンドで利用されているScala製のメッセージキューサーバです。

- memcachedと互換するプロトコルを利用する
- メッセージは永続化される

というような特徴を持っています。

memcachedプロトコルを互換性を持っているため、memcachedクライアントライブラリさえあればKestrelも利用できます。また、メッセージがファイル内に永続化されるため、もしKestrelがクラッシュしたとしてもメッセージの内容が失われることはありません。

メッセージキューサーバにはRabbitMQActiveMQなどの実装がありますが、Kestrelはそれに比べると非常に手軽に扱えます。

Kestrelの導入

ウェブサイトに置いてある、予めビルド済みのパッケージを利用します。

  1. $ wget http://robey.github.com/kestrel/download/kestrel-2.1.5.zip
  2. $ unzip kestrel-2.1.5.zip

起動する前にKestrelが利用するログとspool用のディレクトリを作成します。

  1. mkdir -p /var/log/kestrel 
  2. sudo chown -R (ユーザ名)> /var/log/kestrel
  3. mkdir -p /var/spool/kestrel
  4. sudo chown -R (ユーザ名) /var/spool/kestrel

Kestrelを起動します。

  1. $ cd kestrel-2.1.5
  2. $ sudo java -jar kestrel-2.1.5.jar

特にエラーが出なければ成功です。

PHPからKestrelを扱う

Kestrelはmemcachedと互換するAPIを持っています。PHPからKestrelに接続するには、Memcahcedライブラリを利用できます。

あらかじめMemcachedライブラリをインストールしておいて下さい。

  1. $ pecl install memcahed

では早速Kestrelのhogeキューにメッセージを投げる簡単な例を紹介します。

  1. <?php
  2. // send.php
  3.  
  4. $m = new Memcached();
  5. $m->addServer('localhost', 22133);
  6.  
  7. // hogeというキューに現在の時間を投げる
  8. $m->set('hoge', time());

ウェブサービスでのフロントエンド側に相当します。簡単ですね。

次は、PHPからKestrelのhogeキューからメッセージを取得して表示する例です。

  1. <?php
  2. // consume.php
  3.  
  4. $m = new Memcached();
  5. $m->addServer('localhost', 22133);
  6.  
  7. for (;;) {
  8.     // hogeキューのメッセージが取れるまで2000ミリ秒待つ
  9.     $result = $m->get('hoge/t=2000'); 
  10.  
  11.     if ($result !== false) { // メッセージをキューから取れた場合
  12.         echo $result . 'っていうメッセージを受け取ったよー';
  13.     }
  14. }

バッチ処理を行うバックエンド側のワーカープロセスに相当します。これも簡単ですね。

ここでconsume.phpを起動しておいて、他のターミナルなどからsend.phpをなんどか起動してみましょう。consume.phpの出力を見ているとメッセージをリアルタイムに処理していくのがわかると思います。

ワーカープロセスの管理

さて、PHPとKestrelを利用したタスクキューイングの簡単な例を紹介しました。

上の例では単にPHPを叩いてワーカープロセスを立ち上げていますが、実際に何かのサービスで利用する際には問題があります。

- 手でワーカープロセスを起動する必要がある
- 複数プロセスで起動するのも手でやらなければならない
- ワーカープロセスが突然死したらそのまま

とくにワーカープロセスがバグか何かで突然落ちてしまった場合が困りますね。勝手に再起動かまして何事も無かったかのように動いてくれたほうが良いはずです。これらの問題を解決するのにSupervisorというpython製のプロセス管理ツールを導入します。

Supervisorの導入

easy_installからインストールできます。easy_installが入ってない場合はインストールします。

  1. $ curl -O http://peak.telecommunity.com/dist/ez_setup.py
  2. $ sudo python ez_setup.py

Supervisorをeasy_installからインストールします。

  1. $ sudo easy_install supervisor

次にSupervisor用の設定ファイルを/etc/supervisord.confに書き出して適当に修正します。

  1. $ sudo echo_supervisord_conf > /etc/supervisord.conf
  2. $ sudo vim /etc/supervisord.conf
  3. $ echo_supervisord_conf | diff - /etc/supervisord.conf
  4. < ;[inet_http_server]         ; inet (TCP) server disabled by default
  5. < ;port=127.0.0.1:9001        ; (ip_address:port specifier, *:port for all iface)
  6. ---
  7. > [inet_http_server]         ; inet (TCP) server disabled by default
  8. > port=127.0.0.1:9001        ; (ip_address:port specifier, *:port for all iface)
  9. < ;user=chrism                 ; (default is current user, required if root)
  10. ---
  11. > user=root                    ; (default is current user, required if root)
  12. < ;[include]
  13. < ;files = relative/directory/*.ini
  14. ---
  15. > [include]
  16. > files=/etc/supervisord.d/*.ini
  17. > 

設定用のディレクトリを作ってやります。このディレクトリ以下にiniファイルを置いてやってsupervisordで管理するプロセスを設定していきます。

  1. $ sudo mkdir /etc/supervisord.d

無事インストールできたかどうか確かめてみましょう。

  1. $ sudo supervisord

特にエラーが出なければインストールは成功です。

Supervisorで管理するプロセスを設定する

ではバッチ処理を行うワーカープロセスを管理するための設定を/etc/supervisord.d/hoge-consumer.iniに書きます。

  1. [program:hoge-consumer]
  2. command=php /path/to/consume.php
  3. process_name=%(program_name)s_%(process_num)02d 
  4. numprocs=10 ; ワーカーを10プロセス起動する
  5. autostart=true ; supervisord起動時に自動的に起動する
  6. autorestart=true ; プロセスが死んでも自動的に起動する
  7. user=<ユーザ名>
  8. redirect_stderr=true          
  9. stdout_logfile=/path/to/hoge_consume_task.log ; 標準出力の内容をログに取る

ついでにKestrelの管理もSupervisorでやります。/etc/supervisor.d/kestrel.iniに書きます。

  1. [program:kestrel]
  2. command=java -jar /path/to/kestrel-2.1.5/kestrel-2.1.5.jar
  3. autostart=true    
  4. autorestart=true    
  5. user=root   

これで設定が終わりました。設定を再読み込みします。

  1. $ sudo supervisorctl reload
  2. Restarted supervisord

特にエラーが出なければ成功です。Supervisorで管理しているプロセスの状態を見てみましょう。設定が何か間違っていなければ以下のようになります。kestrelやワーカープロセスがきちんと立ち上がっています。

  1. $ sudo supervisorctl status
  2. kestrel                          RUNNING    pid 1370, uptime 0:01:30
  3. hoge-consumer:hoge-consumer_00   RUNNING    pid 1375, uptime 0:01:30
  4. hoge-consumer:hoge-consumer_01   RUNNING    pid 1374, uptime 0:01:30
  5. hoge-consumer:hoge-consumer_02   RUNNING    pid 1377, uptime 0:01:30
  6. hoge-consumer:hoge-consumer_03   RUNNING    pid 1376, uptime 0:01:30
  7. hoge-consumer:hoge-consumer_04   RUNNING    pid 1379, uptime 0:01:30
  8. hoge-consumer:hoge-consumer_05   RUNNING    pid 1378, uptime 0:01:30
  9. hoge-consumer:hoge-consumer_06   RUNNING    pid 1381, uptime 0:01:30
  10. hoge-consumer:hoge-consumer_07   RUNNING    pid 1380, uptime 0:01:30
  11. hoge-consumer:hoge-consumer_08   RUNNING    pid 1373, uptime 0:01:30
  12. hoge-consumer:hoge-consumer_09   RUNNING    pid 1372, uptime 0:01:30

ここで試しにkestrelのプロセスをkillしてみましょう。

  1. $ ps A | grep kestrel
  2.  1370   ??  S      0:05.19 /usr/bin/java -jar /path/to/kestrel-2.1.5/kestrel-2.1.5.jar
  3. $ sudo kill 1370

Supervisorのステータスを確認します。

  1. $ sudo supervisorctl status
  2. kestrel                          RUNNING    pid 1634, uptime 0:00:02
  3. hoge-consumer:hoge-consumer_00   RUNNING    pid 1375, uptime 0:08:37
  4. (中略)
  5. hoge-consumer:hoge-consumer_09   RUNNING    pid 1372, uptime 0:08:37

プロセスが死んでも勝手にkestrelを再起動してくれています。これでワーカープロセスが突然死しても大丈夫ですね。

最後にsupervisord自体の自動起動の設定を忘れないようにして下さい。当然ですがsupervisordが起動しないとsupervisordで管理しているプロセスも起動しません。

おまけですがSupervisorは以下のキャプチャのようなhttpインターフェイスも備えていて、/etc/suprvisord.confのinet_http_server項目で設定したアドレスにホストされています。ステータスやログを見たりプロセスを再起動したりできます。



終わり

この記事では、PHPとKestrelSupervisorを利用したタスクキューイングの導入を紹介しました。KestrelやSupervisorには、ここで紹介した以外にもいくつか非常に使える機能があります。詳しく知りたい方は本家のドキュメントを参照しましょう。また、Kestrelではなく別のメッセージキューサーバの方がいいとかあったら是非教えて下さい。

参考

- Kestrel
- twitterでも利用されているメッセージキュー Kestrelを試す
- kestrelの作者さんによるkestrelの紹介
- Twitterで使っているScalaで書かれたオープンソースのメッセージキューサーバー、Kestrel
- Supervisor: A Process Control System
- スーパーサーバーSupervisorの導入手順メモ
- SuperVisor導入(基本編)
- FreeBSDで Supervisor を使う
- プロセス管理にsuperviserを使う