オープンソースこねこね

Webプログラミングなどについてあれこれ。

PHP Webアプリケーションから非同期にコマンドを実行するライブラリをつくりました。

こんなの作りました。

BackgroundProcess

Webのプロセスから重い処理を実行したいのだけど。。。

PHPでCSVファイルなどから一括で大量のデータ投入を行いたいときがあります。 これをWebアプリケーションのプロセスでやろうとすると、処理時間が長いため、だいたいプロセスがタイムアウトしてエラーになってしまいます。困ったものです。

こういうとき、代わりにコマンドラインのプログラムとして実装したりするんですが、任意のタイミングで処理したい場合、コマンド実行する以上サーバにターミナルでログインできる技術者しかデータ投入作業ができなくなってしまいます。 もっとだれにでもできるように、やっぱりWebアプリケーションのインターフェースが欲しくなるのが人情です。

Webアプリから非同期にコマンドを実行する

この要件を満たす簡単な方法は、以下のようなコードでWebプロセスから外部コマンドをバックグラウンドで実行することです。

exec("php yourcommand.php > /dev/null &");

こうすることで実行コマンドは非同期にバックグラウンドで動作し、Webのプロセスはコマンドの処理内容にかかわらず、即時レスポンスを返すことができます。

ただこれだと、以下のような不満があります。

  • 外部コマンドのエラー処理が考えられていない。コマンドごとにそれぞれ内部で何らかの実装をする必要がある。
  • 実行開始したコマンドのプロセスをWebプロセス側から関与することができない。

こういうことをうまく扱うために、ジョブキューエンジンや、DBなどを使ったタスクやプロセスを管理する仕組みはすでにあると思います。 でもそこまで手間をかけたり、構成を複雑にしたくない。

ちょっとだけ発展させてみました

というわけで作ったのが、

BackgroundProcess

インストールはComposerで以下のようにcomposer.jsonを作成して

{
  "require": {
    "kohkimakimoto/background-process": "1.1.*"
  }
}

インストールコマンドを実行。

$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install

使い方は、実行したいコマンドをコンストラクタに指定してオブジェクトを生成した後runメソッドを実行するだけです。

use Kohkimakimoto\BackgroundProcess\BackgroundProcess;

// Creates instance and set command string to run at the background.
$process = new BackgroundProcess("ls -l > /tmp/test.txt");
// Runs command, and it returns immediately.
$process->run();

// Get key identified the process.
$key = $process->key();

$process->key()でプロセスを識別する一意キーが取得できます。 これはあとで実行したコマンドが実行中であるかなどを確認するのに以下のように使います。

use Kohkimakimoto\BackgroundProcess\BackgroundProcess;

$manager = new BackgroundProcessManager();
$process = $manager->loadProcess($key);

// If a process specified by the key dosen't exist, loadProcess method returns null.
if (!$process) {
  echo "Not working process $key";
} else {
  $meta = $process->getMeta();
  echo $meta['created_at'];   // (ex 2013-01-01 10:00:20
  echo $meta['pid'];          // (ex 1234
}

また、コマンドがエラーになった場合デフォルトで下のパスのログファイルにエラーログ(標準エラー出力)を出力します。

/tmp/php/background_process/err.log

内部の作り

前述したように、手軽に使えるようにしたかったのでDBなどは使っていません。 BackgroundProcessはrunメソッドを実行すると、デフォルトで/tmp/php/background_process/配下にコマンドごとに、PHPファイルとJSONファイルを出力します。 PHPファイルはコンストラクタで指定したコマンドの実行を行うスクリプトで、JSONファイルはプロセスIDなどのメタデータを保持しています。 これらのファイルを利用してコマンド実行やエラー処理、プロセスIDの取得などを行っています。 コマンドが正常終了すると、これらのファイルは自動削除されます。

シーケンス図にすると以下のようになります。

20130812192611