動機と背景
下記のような理由から、RabbitMQでメッセージキューイングを試してみたので、記録として残しておきます。
- PHP + Silex(Symfony2.3+)で、BtoBtoCなWebアプリケーションを開発・運用している
- 管理画面で時間のかかる処理がある(CSVファイルのダウンロードなど)
- データベースに、キューイング用のテーブルを作って、cronで一定間隔でキューがあるかチェックする、というような実装をしている
- cron使いたくない(1分おきに実行とかなんとなく)
- もっと汎用的な仕組みに置き換えたい
RabbitMQ
メッセージキューイングで調べると下記のようなライブラリが見つかります。
チームのエンジニアに意見を聞いて、RabbitMQは使いやすかったということだったので、試してみることにしました。
php-amqplib
RabbitMQのClients and Developer Toolsを見ると、PHP用のライブラリがいくつかあります。
ただ、公式のtutorialを見ると、php-amqplibが使われているので、これを採用しました。
CUI版のexampleを作成
ほぼほぼ、tutorialにしたがって、CUI版のexampleを作りました。
https://github.com/imunew/php-rabbitmq-example
ざっとポイントを列挙すると、以下のようになります。
- vagrant + ansible で実行環境構築を自動化
- コンソールを2つ起動し、1つは受信用、1つは送信用として使う
- 送信側でスクリプトを実行すると、受信側にメッセージが表示される
GUI(Web)版のexampleを作成
次に、GUI(Web)版を実装してみました。
https://github.com/imunew/silex-rabbitmq-example
より実戦に近いイメージで、実装をしてみました。
CUI版との違いを列挙すると、以下のようになります。
- supervisorで受信側スクリプトを常駐化(デーモン化)
- redisを経由して、処理結果を画面側に渡すようにした
- 重たい処理は
usleep(1000000);
を10回実行することで再現
ハマったところ
メッセージをどう管理するか
複数のメッセージを非同期で扱うことになるので、一つ一つのメッセージがそれぞれユニークになる必要があります。
exampleでは、画面表示したときに、md5(time())でハッシュを作り、それをメッセージのIDとするようにしました。
// src/controllers.php
$app->get('/example', function (Request $request) use ($app) {
$result = $request->get('result', []);
/**
* 初期表示のときに、md5(time())でハッシュが作られ、それがメッセージのIDとなる
*/
$hash = $request->get('hash', md5(time()));
$startTime = $request->get('startTime', '');
$endTime = $request->get('endTime', '');
return $app['twig']->render('app/example.html.twig', [
'result' => $result,
'hash' => $hash,
'startTime' => $startTime,
'endTime' => $endTime,
]);
})
;
supervisorのセットアップ
正直ハマりましたw。
yumでインストールすると、2.x系がインストールされるので、easy_installから(3.x系を)インストールします。
supervisordコマンドで常駐化するというのも、はじめ分かりませんでした。
最終的には、ansibleに手順がまとまっていますので、参照ください。
ansible/roles/supervisor/tasks/main.yml
使いどころや課題など
チームで話し合って実戦投入するか決めたいと思いますが、なんとなく課題になりそうなところを想像してみました。
- 使いどころ
- 重たいドキュメント系処理(CSV、PDF出力)
- メール送信(spoolしておいて非同期に送信)
- 非同期にするだけで、単純に実装の難易度あがる
- いい感じにパッケージ化してあげないと、オレオレな実装が増えそう
- 受信側プロセスをどこで動かすか
- 各Webサーバーに分散するか、中央集権的なサーバーを作るか
- どこで処理しても、結果を受け取れなければいけない
実戦で投入したら、記事を更新しようと思います。
exampleの実装などで、ご意見ご感想ありましたら、githubでpull-requestやissueください。
もちろん、本投稿にコメントいただいても結構です。