むかーしの話、PHPでデーモンを一本つくるはめになってpnctl関数群をちまちま使ってこさえたときは、なんだかちょっと苦労した。Javaやperlのほうがまだ作りやすかったような。 しかし今は、PHPであってもPEARにSystem_Daemonという便利な拡張が用意されているので、これを使うとラクにデーモンスクリプトを自作できる。
なお、同じdaemonでもいわゆるネットワークサーバの類はNet_Serverのほうが向いているのだが、その話はまたの機会に。
また、ここから先の話はすべてPHPが—enable-pcntlオプション付きでコンパイルされているのが前提。供用レンタルサーバで提供されている PHPではたいてい無理。たくさんのユーザーに好き勝手に常駐プロセス走らされたらCPU負荷的にひどいことになりそうだから当然だ。できれば専用サーバを使いましょう。
さて、—enable-pcntlしたPHPの実行環境にSystem_Daemonと、ついでにLog(これってデフォルトで入ってるんだっけ?pear listコマンドで確認可能)をインストールしたら、さっそくコードを書いてみよう。
require_once("Log.php"); require_once("System/Daemon.php"); // ログ出力用のオブジェクト。syslogのlocal4に吐く。レベルは7(デバッグ)つまり全部。 $logger = &Log::singleton('syslog', LOG_LOCAL4, "testtest", null, 7); $options = array( "appName" => "testdaemon" // このデーモンの名前 ,"appDescription" => "pear system daemon test" // 説明 ,"appDir" => dirname(__FILE__) // デーモンが動作するときのカレントディレクトリ ,"authorName" => "neta.ywcafe.net" ,"authorEmail" => "root@localhost" ,"sysMaxExecutionTime" => "0" ,"sysMaxInputTime" => "0" // ,"appRunAsUID" => 501 // このデーモンの実行ユーザーとグループ // ,"appRunAsGID" => 501 // ここではコメントアウトにしてるが必ず指定すべき // ,"appPidLocation" => "/tmp/foo/bar/testdaemon/testdaemon.pid" // ↑指定しなければ/var/run/上でappNameというディレクトリと*.pidファイルをつくろうとする ,"usePEARLogInstance" => $logger // 下の説明参照。 ); System_Daemon::setOptions($options); System_Daemon::start(); //DBに接続するならこのへんで。つまりstart()のあとで。 $loop = 0; while ( ! System_Daemon::isDying()) {// デーモンが停止処理中でないかループのたびにチェック $loop++; // //このへんでなんでも好きな処理をする // // なんかログを吐いてみる。(system_daemon正式版) System_Daemon::debug("ほげほげ"); // なんかログを吐いてみる。 //(どうせPearLogのシングルトンなんだから$loggerを直接呼んでもいいじゃないか版) $logger->debug("hoge! ".date("r")); if ($loop % 30 == 0) { $logger->info("現在のメモリ使用量: ".memory_get_usage()); } System_Daemon::iterate(5); } System_Daemon::stop();起動方法: php コード名.php
停止方法: ps コマンドでプロセス番号を探してそれをkill。
下で述べる起動スクリプトを使うと sudo /etc/init.d/hogehoge start|stop とかでやれるようになる。以下はコードのコメントでは書ききれなかったメモ。
動作状況は必ずログに吐くべし。いわゆるprintfデバッグもなるたけ避ける。
デーモンというものはバックグラウンドのプロセスなので何か起きてもそれを叫ぶ場がない。たとえば普通の画面処理ではないのでブラウザに何か表示されるわけでももちろんない。したがって、通常の動作結果や何らかの予期せぬ障害発生の報告はすべてログに吐かせる必要がる。System_Daemonの作者のサイトでも、「メインの無限ループ内部でechoすんな。本当にバックグラウンドで走ってるときにSTDOUTに吐くような動作(echoとか)をうっかりさせたらfatalエラーで死ぬよ」と解説しているとおりである。
なお、SYSTEM_DAEMON自体に独自にファイルにログを吐く仕組みが用意されているのだが、筆者はsyslogに吐くほうが好みであり、かつ他の 画面やバッチのアプリのログ出力機構と共通化を図るためにも、単純にPear/Logとsyslogを組み合わせて使っている。このほうが何かと簡単かつ 単純だ。
実行ユーザー/グループを設定する
これはデーモンに限らず言えることだが、このデーモンスクリプトはどの実行ユーザー/グループで動く(べき)のか、ということをきちんと検討したうえでそ れをしかるべくコード上で表現しておいたほうがいい。余計な混乱を避けれる。設定しなければそれを叩いたユーザーの権限で動くだけだが、実運用ではデーモ ンはinitスクリプト等で自動起動されるケースが多い。それでほっとくroot権限で動くということになり、その状況はまったくおすすめできない。
このスクリプトのファイル自体をchmod 755しておかないと起動スクリプトを作ってくれない
ここに書いてあるように System_Daemon::writeAutoRun();と やると、 /etc/init.d/の配下に起動スクリプトを自動的に作ってくれるという気の利いた機能がついている。 ただし、そのデーモンのスクリプトのファイル自体に実行属性がついてることを前提としたつくりになっている。これも好みの問題かもしれないが、 「php スクリプト名.php」というふうに指定して実行するほうがいいと思うので、起動スクリプトができあがったらその「/usr/local/bin/php /パス/スクリプト名.php」といった風に書き直しておいたほうがいい。
ループ制御
デーモンというものは内部で無限ループすることでいわゆる「常駐」状態となる。 System_Daemon::isDying() の返す値でループを続けるか否かを判断することで、スクリプトが停止のシグナルを受けたときに処理状態が中途半端な状態で停止してしまうことをある程度防 ぐことができる(らしい、ってとこまでしかコード読んでないんだけど)。
また、ループの最初または最後で必ず System_Daemon::iterate(秒数) する。このメソッドは内部で指定秒数だけsleepし、かつ、clearstatcacheを呼んで余計な内部キャッシュを掃除してくれる。秒数をゼロにするのはあまりおすすめできない。CPUを食いすぎる可能性が高まるからだ(そのデーモンに何をやらせるかにもよるけど)。
メモリ使用量
ループの内部でうっかり同じオブジェクトや配列に要素を追加し続けたりすると、当然ながらそのデーモンのプロセスが抱えこむメモリ使用量は増え続けてしま う。一種のリーク状態だ。作る側が気をつけるしかないが、手っ取り早い方法は、memory_get_usage()の結果を時々ログに吐いて観察してみ ること。
シグナルハンドラ
できればいろんなシグナルを受け取った時の動作をSignalHandlerとしてちゃんと書くのがベターなんだけど、ここでははごく簡単なサンプルということであしからずご了承ください。
その他もろもろの諸注意は System_Daemonの作者のサイトへ。
常駐プロセス(デーモン)として動くアプリを自作する(PHPとPEAR:: System_Daemon編)
PEAR::System_Daemon、便利ですね
