こんにちは、久保田です。
ウェブサービスでは、ユーザのアクションに従ってバッチ処理を行わなければならないケースがままあります。この記事では、バッチ処理の手法の一つであるタスクキューイングをPHPとKestrelとSupervisorを利用して行うやり方の導入を紹介します。
なぜなにタスクキューイング
ウェブサービスでは、ユーザのアクションに従って非同期にバッチ処理を行うようなケースがよくあります。
例えばflickrのような写真を共有するウェブサービスで言えば、ユーザが写真をアップロードしたあとに非同期でその画像の複数のサムネイル生成や加工をしなければならないケースがあります。
よく見られるのは、DBにバッチ処理のためのタスクデータを入れておいて、後でcronで定期的に起動するワーカープロセスからバッチ処理を行う方法です。このやり方には、ワーカーを複数プロセスで扱いづらい、処理がリアルタイムになされない、という欠点があります。
これとは別に、メッセージキューを使ってバッチ処理をユーザのアクションとは非同期に行うやり方があります。ウェブサービスのフロントエンド側では、メッセージキューにタスクに関するデータを投げ込んでおいて、実際のバッチ処理などはバックエンドに常に控えているワーカープロセスがリアルタイムにバッチ処理を行います。
以下では、メッセージキューサーバであるKestrelとPHPとSupervisorを使うお手軽タスクキューイングの導入を紹介します。
Kestrelとは
Kestrelは、twitterのバックエンドで利用されているScala製のメッセージキューサーバです。
- memcachedと互換するプロトコルを利用する
- メッセージは永続化される
というような特徴を持っています。
memcachedプロトコルを互換性を持っているため、memcachedクライアントライブラリさえあればKestrelも利用できます。また、メッセージがファイル内に永続化されるため、もしKestrelがクラッシュしたとしてもメッセージの内容が失われることはありません。
メッセージキューサーバにはRabbitMQやActiveMQなどの実装がありますが、Kestrelはそれに比べると非常に手軽に扱えます。
Kestrelの導入
ウェブサイトに置いてある、予めビルド済みのパッケージを利用します。
$ wget http://robey.github.com/kestrel/download/kestrel-2.1.5.zip
$ unzip kestrel-2.1.5.zip
起動する前にKestrelが利用するログとspool用のディレクトリを作成します。
mkdir -p /var/log/kestrel
sudo chown -R (ユーザ名)> /var/log/kestrel
mkdir -p /var/spool/kestrel
sudo chown -R (ユーザ名) /var/spool/kestrel
Kestrelを起動します。
$ cd kestrel-2.1.5
$ sudo java -jar kestrel-2.1.5.jar
特にエラーが出なければ成功です。
PHPからKestrelを扱う
Kestrelはmemcachedと互換するAPIを持っています。PHPからKestrelに接続するには、Memcahcedライブラリを利用できます。
あらかじめMemcachedライブラリをインストールしておいて下さい。
$ pecl install memcahed
では早速Kestrelのhogeキューにメッセージを投げる簡単な例を紹介します。
<?php
// send.php
$m = new Memcached();
$m->addServer('localhost', 22133);
// hogeというキューに現在の時間を投げる
$m->set('hoge', time());
ウェブサービスでのフロントエンド側に相当します。簡単ですね。
次は、PHPからKestrelのhogeキューからメッセージを取得して表示する例です。
<?php
// consume.php
$m = new Memcached();
$m->addServer('localhost', 22133);
for (;;) {
// hogeキューのメッセージが取れるまで2000ミリ秒待つ
$result = $m->get('hoge/t=2000');
if ($result !== false) { // メッセージをキューから取れた場合
echo $result . 'っていうメッセージを受け取ったよー';
}
}
バッチ処理を行うバックエンド側のワーカープロセスに相当します。これも簡単ですね。
ここでconsume.phpを起動しておいて、他のターミナルなどからsend.phpをなんどか起動してみましょう。consume.phpの出力を見ているとメッセージをリアルタイムに処理していくのがわかると思います。
ワーカープロセスの管理
さて、PHPとKestrelを利用したタスクキューイングの簡単な例を紹介しました。
上の例では単にPHPを叩いてワーカープロセスを立ち上げていますが、実際に何かのサービスで利用する際には問題があります。
- 手でワーカープロセスを起動する必要がある
- 複数プロセスで起動するのも手でやらなければならない
- ワーカープロセスが突然死したらそのまま
とくにワーカープロセスがバグか何かで突然落ちてしまった場合が困りますね。勝手に再起動かまして何事も無かったかのように動いてくれたほうが良いはずです。これらの問題を解決するのにSupervisorというpython製のプロセス管理ツールを導入します。
Supervisorの導入
easy_installからインストールできます。easy_installが入ってない場合はインストールします。
$ curl -O http://peak.telecommunity.com/dist/ez_setup.py
$ sudo python ez_setup.py
Supervisorをeasy_installからインストールします。
$ sudo easy_install supervisor
次にSupervisor用の設定ファイルを/etc/supervisord.confに書き出して適当に修正します。
$ sudo echo_supervisord_conf > /etc/supervisord.conf
$ sudo vim /etc/supervisord.conf
$ echo_supervisord_conf | diff - /etc/supervisord.conf
< ;[inet_http_server] ; inet (TCP) server disabled by default
< ;port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface)
---
> [inet_http_server] ; inet (TCP) server disabled by default
> port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface)
< ;user=chrism ; (default is current user, required if root)
---
> user=root ; (default is current user, required if root)
< ;[include]
< ;files = relative/directory/*.ini
---
> [include]
> files=/etc/supervisord.d/*.ini
>
設定用のディレクトリを作ってやります。このディレクトリ以下にiniファイルを置いてやってsupervisordで管理するプロセスを設定していきます。
$ sudo mkdir /etc/supervisord.d
無事インストールできたかどうか確かめてみましょう。
$ sudo supervisord
特にエラーが出なければインストールは成功です。
Supervisorで管理するプロセスを設定する
ではバッチ処理を行うワーカープロセスを管理するための設定を/etc/supervisord.d/hoge-consumer.iniに書きます。
[program:hoge-consumer]
command=php /path/to/consume.php
process_name=%(program_name)s_%(process_num)02d
numprocs=10 ; ワーカーを10プロセス起動する
autostart=true ; supervisord起動時に自動的に起動する
autorestart=true ; プロセスが死んでも自動的に起動する
user=<ユーザ名>
redirect_stderr=true
stdout_logfile=/path/to/hoge_consume_task.log ; 標準出力の内容をログに取る
ついでにKestrelの管理もSupervisorでやります。/etc/supervisor.d/kestrel.iniに書きます。
[program:kestrel]
command=java -jar /path/to/kestrel-2.1.5/kestrel-2.1.5.jar
autostart=true
autorestart=true
user=root
これで設定が終わりました。設定を再読み込みします。
$ sudo supervisorctl reload
Restarted supervisord
特にエラーが出なければ成功です。Supervisorで管理しているプロセスの状態を見てみましょう。設定が何か間違っていなければ以下のようになります。kestrelやワーカープロセスがきちんと立ち上がっています。
$ sudo supervisorctl status
kestrel RUNNING pid 1370, uptime 0:01:30
hoge-consumer:hoge-consumer_00 RUNNING pid 1375, uptime 0:01:30
hoge-consumer:hoge-consumer_01 RUNNING pid 1374, uptime 0:01:30
hoge-consumer:hoge-consumer_02 RUNNING pid 1377, uptime 0:01:30
hoge-consumer:hoge-consumer_03 RUNNING pid 1376, uptime 0:01:30
hoge-consumer:hoge-consumer_04 RUNNING pid 1379, uptime 0:01:30
hoge-consumer:hoge-consumer_05 RUNNING pid 1378, uptime 0:01:30
hoge-consumer:hoge-consumer_06 RUNNING pid 1381, uptime 0:01:30
hoge-consumer:hoge-consumer_07 RUNNING pid 1380, uptime 0:01:30
hoge-consumer:hoge-consumer_08 RUNNING pid 1373, uptime 0:01:30
hoge-consumer:hoge-consumer_09 RUNNING pid 1372, uptime 0:01:30
ここで試しにkestrelのプロセスをkillしてみましょう。
$ ps A | grep kestrel
1370 ?? S 0:05.19 /usr/bin/java -jar /path/to/kestrel-2.1.5/kestrel-2.1.5.jar
$ sudo kill 1370
Supervisorのステータスを確認します。
$ sudo supervisorctl status
kestrel RUNNING pid 1634, uptime 0:00:02
hoge-consumer:hoge-consumer_00 RUNNING pid 1375, uptime 0:08:37
(中略)
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とKestrelとSupervisorを利用したタスクキューイングの導入を紹介しました。KestrelやSupervisorには、ここで紹介した以外にもいくつか非常に使える機能があります。詳しく知りたい方は本家のドキュメントを参照しましょう。また、Kestrelではなく別のメッセージキューサーバの方がいいとかあったら是非教えて下さい。
参考
- Kestrel
- twitterでも利用されているメッセージキュー Kestrelを試す
- kestrelの作者さんによるkestrelの紹介
- Twitterで使っているScalaで書かれたオープンソースのメッセージキューサーバー、Kestrel
- Supervisor: A Process Control System
- スーパーサーバーSupervisorの導入手順メモ
- SuperVisor導入(基本編)
- FreeBSDで Supervisor を使う
- プロセス管理にsuperviserを使う