Gearmanでqueueing

January 31, 2010 - PHP

queueing

webページを生成する際には、今すぐやらなきゃいけないことと、今すぐでなくても良いものがあります。
今すぐやらなきゃいけないこととは、ユーザーリクエストに対するDB参照結果等のことで、今すぐでなくても良いものとはアクセスログなどの処理をDBに書き込んだりメール送信したりなど。それ以外にもあるかもですが大体そんな感じです。

通常であればすべて一回のHTTPリクエストの中でやるわけですが、queueingをすれば今やるべきでないことを後回しにできます。他にもメリットはありますが割愛で。

で、phpでqueuingをやるとしたら、こんなのがあるそうです。

で、Q4Mを試そうとしたらMySQL5.1以降でないとダメとか。
ちょっとした事情で「5.1以降」という制約は避けたいので、Gearmanを試してみる事に。
※最後のbeanstalkdはlithiumから使えるみたいです(li3_queue)。

仕組み

図解は公式サイトの図を見て下さい。

登場人物は大きく3つ。

  • ・Client:処理を依頼する人
  • ・Worker:依頼された処理を実行する人
  • ・JobServer:ClientとWorkerの橋渡しをする人

JobServerはdaemonで常駐していて、
Clientからの要求がある度に、別途常駐しているWorkerに対して処理を投げます。
かなりシンプルです。

使い道

Client側から処理を依頼するパターンはだいたい4つ。

  • 今すぐ依頼して、その結果を受け取る
  • 今すぐ依頼して、結果を待たずに終了する
  • タスクを追加して、最後にまとめて実行して、その結果を受け取る
  • タスクを追加して、最後にまとめて実行して、結果を待たずに終了する

インストール

Gearmanサーバーのインストール

wget http://launchpad.net/gearmand/trunk/0.11/+download/gearmand-0.11.tar.gz
tar zxf gearmand-0.11.tar.gz
cd gearmand-0.11
./configure
make; make install

起動

/usr/local/sbin/gearmand -u root –daemon

php extensionのインストール

wget http://pecl.php.net/get/gearman-0.6.0.tgz
tar zxf gearman-0.6.0.tgz
cd gearman-0.6.0
phpize
./configure
make; make install

サンプル

今すぐ依頼して、結果を受け取るパターン

worker.php

<? php
$worker = new GearmanWorker();
$worker->addServer();
$worker-&gt;addFunction('hoge','hoge_func');
// 常駐
while($worker-&gt;work());

function hoge_func(GearmanJob $job)
{
    return 'hoge'.$job-&gt;workload();
}

<? php

client.php

<? php
$worker = new GearmanWorker();
$worker->addServer();
echo  $client-&gt;do('hoge', 'hello');
echo "\n";

実行してみる

$ php woker.php &
$ php client.php
hello, hoge

これだけだとほぼ意味ないので、重い処理を遅延実行させてみます。

今すぐ依頼して、結果を待たずに終了するパターン

worker.php

<? php
$worker = new GearmanWorker();
$worker-&gt;addServer();
$worker-&gt;addFunction('hoge','hoge_func');
$worker-&gt;addFunction('heavy','heavy_func');
// 常駐
while($worker-&gt;work());

function hoge_func(GearmanJob $job)
{
    return 'hoge, '.$job-&gt;workload();
}
function heavy_func(GearmanJob $job)
{
        echo "wait...";
        sleep(10);
    return 'hoge, '.$job-&gt;workload();
}

client.php

<? php
$client = new GearmanClient();
$client->addServer();
echo  $client-&gt;do('hoge', 'hello');
echo "\n";
echo $client-&gt;do('heavy', 'hello (sync)');
echo "\n";
$client-&gt;doBackground('heavy', 'hello (async)');
echo "\n";

実行してみる

$ php worker.php &
$ php client.php
hoge, hello
hoge, hello (sync)

とまぁ、doBackgroundで依頼したキューの場合、clientでは結果を受け取ってません。
これが基本的な使い方。

タスクを追加して、最後にまとめて実行して、その結果を受け取る

task_client.php

<? php
$client = new GearmanClient();
$client->addServer();
$client->setCompleteCallback('task_cb');
$client->addTask('hoge', 'arg1');
$client->addTask('heavy', 'arg2');
$client->runTasks();
function task_cb(GearmanTask $task)
{
    echo '[result]'.$task->data();
    echo "\n";
}

$ worker.php &
$ task_client.php
[result]hoge, arg1
[result]hoge, arg2

と、ひとつめの結果表示のあとには重い処理待ちが発生します。

タスクを追加して、最後にまとめて実行して、結果を待たずに終了する

重い処理をバックグラウンドのタスクとして登録します。
task_client.php

<? php

$client = new GearmanClient();
$client->addServer();
$client->setCompleteCallback('task_cb');
$client->addTask('hoge', 'arg1');
$client->addTaskBackground('heavy', 'arg2');
$client->runTasks();

function task_cb(GearmanTask $task)
{
        echo '[result]'.$task->data();
        echo "\n";
}

実行

[result]hoge, arg1

addTaskBackgroundで追加したタスクの結果のみ待たずに処理が終了します。
この例だと意味ないですが、今すぐ結果を必要としない処理を後回しにすることでユーザーへのレスポンスを高速化することができます。

注意点

引数は数字と文字列のみ

配列やオブジェクトではやりとりできないので、
そういったものはシリアライズするとかjsonにするとかして渡さないとだめです。

workerが見つかるまで待つ

clientからリクエストするworker名が存在しない(常駐していない場合)は、
JobServerはworkerが見つかるまで待ちます。clientがバックグラウンドで依頼しなければそちらでも待ちが発生します。で、対象workerを起動すると即座にJobServerはキュー処理をworkerに渡します。当然ですが。

もう少し現実的な処理の中でqueueingして試したかったのですが、
まずは入り口ということで。次回はもうちょっと突っ込んだ処理をやってみたいと。

参考資料