時間のかかる処理や言語間の連携をGearmanで

## まえおき
[Gearman](http://gearman.org/)というジョブキューサーバを使って時間のかかる処理や言語間の連携をやってみようというお話

Express(node.js)上のWebアプリからExcelファイルをごにょごにょして返したいという要望が出たとき、使い慣れた[PHPExcel](https://phpexcel.codeplex.com/)でやれたら便利だったりします
node.jsからPHPのプログラムをキックして結果を受け取る方法はパイプを使うとか色々あるのですがWebアプリなので結果を書き出すまでに時間がかかってタイムアウトなんてことは避けたいわけです
そこで昔Webブラウザのボタン押下をトリガにしてメールを大量に配信するときに使っていたGearman使えるじゃんというイージーな方法を思いついたのでした

GearmanはもともとCで書かれたジョブキューサーバです
私は前述のメール配信や画像処理を書いていた2008年頃から使いはじめてた気がします
歴史があるおかげかJava/node.js/PHP/Python/Ruby/Perl/.NET/Go/Lispといろんな言語のバインディングがあるので助かります

ここでは、node.jsのクライアントからPHPで書かれたジョブを実行して結果を受け取る、というようなものをサンプルで書いておきます
逆にPHPからnode.jsで書かれたジョブを実行したり、同じ言語でも時間のかかる処理やブラックボックス化しておきたい処理があるときなども本体のプログラムからは分離して専用のジョブにしてみるなど色々と使い手がある方法だと思います


## インストール
まずはGearmanのインストールですが、CentOSの場合はepelにあるのでyumで

```bash
sudo yum install gearmand
sudo service start gearmand

```
これでデフォルトだとlocalhostの4730番ポートでListenするサービスが立ち上がります

各言語のクライアントをインストール
まずPHPの場合はremiから入れてる場合は

```bash
sudo yum --enablerepo=remi,remi-php56 install php-pecl-gearman

```
で入ってくれるはず・・・PHPのバージョンが違う場合は注意してください
node.jsの場合は

```bash
npm install node-gearman

```
実際はpackage.jsonに追加することになると思います


## 実際のコード
まずはクライアントコードを

```javascript:client.js
var Gearman = require('node-gearman');
var gearman = new Gearman('localhost', 4730);

var job  = gearman.submitJob('example', JSON.stringify({message: 'Hello'}));
var chunks= [];
job.on('data', function(chunk){
   // データは断片的に来る可能性がある
   chunks.push(chunk);
});

job.on('error', function(err){
    console.error(err);
});

job.on('end', function(){
    // ↓は文字列の場合
    // バイナリとかの場合はまた別途考える必要がある
    var buffer = Buffer.concat(chunks);
    console.log(JSON.parse(buffer.toString('utf-8')));
});

```
次にサーバ側は渡されたデータの後ろにWorld!をくっつけて返すという単純なものにしますが、これを「面倒くさい」「この言語に優れたライブラリがあるのでそれでやったほうが書きやすい」「時間のかかる処理」と置き換えればOK

```php:worker.php
<?php
$worker = new GearmanWorker();
// デフォルトだとlocalhost:4730につなぎにいく
// ちゃんと指定する方法もあるよ
$worker->addServer();

// exampleという名前のジョブを無名関数で定義
$worker->addFunction('example', function($job){
    // 基本パラメータはJSONであっても一度シリアライズした状態でやりとりすることになります
    // json_decodeの第二引数をtrueにすると$paramsは配列になる
    // デフォルトだとfalseでstdObjectなオブジェクト形式になる
    $params = json_decode($job->workload(), true);

    // 今回は単純だけどこの辺は色々面倒くさい処理を書いてね！
    $params['message'] = sprintf("%s World!", $params['message']);
    // データを投げて
    $job->sendData(json_encode($params));
    // 終わり
    // 途中経過を%d/%dで送れる sendStatus()メソッドや、失敗の場合はsendFail('エラーメッセージ')なんてのもあります
    $job->sendComplete('');

    // このreturn文、sendXX系のメソッドが出る前の遺物だと思うのですが必要なのかしら
    return GEARMAN_SUCCESS;
});

// これがないと定義しただけで終わってしまう
while($worker->work());

```
あとはターミナルその1で

```bash
php worker.php

```
で起動してる状態にしといて、別のターミナルで

```bash
node client.js

```
で結果が確認できます

## 非同期のメリットを生かす
さて、node.js側は非同期になってるのでexpressなどのWebアプリの場合はsubmitJob()を送った時点でブラウザには「処理は依頼した、結果は追って知らせる」的レスポンスを返しておき、workerの中やクライアントのjob.on('*')なメソッド中でメールなりWebSocket経由で結果を通知するという方法もとれます
ブラウザからサーバ側での動画のエンコードなど時間のかかるバッチ処理なんかをキックさせたい場合は、XHRを使ってもHTTPでやってる以上タイムアウトは発生するので・・・

## ジョブの永続化
デフォルトだとgearmandは投げられたジョブを自分で確保しているメモリストレージに格納しています
なので、うっかり

```bash
service gearmand restart

```
なんてするとたまっているジョブをすっかりド忘れしてくれるお茶目さんです(どうやってもエラーしか出なかった「黒歴史」ジョブも忘却してくれるのでデバッグ中はこっちのほうがありがたいのですが）
キューの永続化にmemcachedやMySQLなどの各種DB、libdrizzleを経由する方法もあるので実運用にはそちらをお勧めします
またGearman自体に簡単なHTTPサーバを持たせてHTTPでジョブを投げちゃえという拡張もあるので公式サイトのマニュアルを確認してください
ちなみに永続化すると、キューを共有した上で複数のサーバで同じワーカーを動かして分散処理なんてこともできます

## 余談
node.js専用だと思ってた[pm2](https://github.com/Unitech/pm2)ですが、.phpなファイルはphpコマンドで実行してくれることが発覚（マニュアル嫁って話なんですが）
おかげでworker.phpなんかの起動も任せられるので色々捗ってます