cronジョブで複数のPHPを非同期実行する話
cronジョブって便利ですよね。
1日に1回、サーバーの負荷が低い時間帯にDBをバックアップしたり、
(参考記事:PHPでファイルとDB(PostgreSQL)をバックアップ)
数時間に1回、RSSフィードを収集したり、
(参考記事:curl_multiは確かに爆速だった)
ほかにもまだ記事にはしていませんが、Apacheログの解析をしたり、解析の終わったログを後でDLしやすいようにZIP化したり、MySQLテーブルの最適化したり。あぁそうそう、エラーログが発生していたらメールする処理※なんかも大事ですね。
※たまにPHPでエラーが発生するたびに管理者に即メールするシステムを見かけますが、あれって即時対応を求められる業務用ならまだしも個人レベルだとけっこう微妙かも知れません。
1日に10万PV以上のサイトを運営していた頃、夜間にレンタルサーバーのDBに障害が発生した影響で、朝になったらメールボックスに6万通のエラーメールが溜まっていて、1日仕事にならなかった経験があります(;´∀`) それ以来、数分~1時間ごとにログをチェックして、まとめて報告する仕組みに書き換えました。
cronの設定画面
自前のWebサーバーやVPSならcrontabコマンドや、cronのファイル自体を直接編集すると思いますが、レンタルサーバーの場合は大抵このような画面で設定すると思います。
(毎朝5時10分に実行される設定例)
ここに直接コマンド、例えば
■シェルスクリプト例
#!/bin/sh /usr/local/bin/php -q /virtual/[アカウント名]/daily.php &> /virtual/[アカウント名]/daily.log exit(&>は標準出力とエラー出力の両方をファイルに書き出すという意味)
ぼくはPHP大好きマンですし、何よりPHPで書いておけばLinuxでもWindowsでも同じように動作させることができるので、最初に書いたようなバックアップ等のメンテナンス処理も基本的にPHPスクリプトで実行しています。
レンタルサーバーのプランによってcron数は異なる
本来は先述したようなシェルスクリプト内に実行するコマンドを列挙するのが普通なのかも知れませんが、うちではPHPスクリプト内から別のPHPスクリプトを呼び出す感じで使っています。
10年以上Web運営を続けているとサイトの数も増えるし、レンタルサーバーもいろんな種類を試してみたくなります。
そんなことをしていると、たまにcronジョブが数個しか登録できないプランだったことに(後から)気がついて難儀することがありました。まぁ、1個でもcronジョブが登録できるなら、それを5分ごとの実行とかに設定して、PHP内部で時間判定すれば良いんですけど。
(VALUE-SERVERのまるっとプランとかcronジョブが1個しか作れません)
そういった経験もあって1日に1回実行するのはdaily.phpにまとめて、1時間に1回はhourly.phpにまとめて、みたいな感じで使うことが多いです。
(それで足りなければ自宅内で24時間稼働させているWindows PCからPHPをキックするようにしたり)
PHPからPHPを非同期実行する具体例
cronで動かしたいものってメンテナンス関係とか、実行時間が長めになるものが多いと思います。DBやファイルのバックアップしかり、他サイトとの通信しかり。
そういう場合は、それぞれの役割ごとにPHPスクリプトを分けて、下記のように非同期実行すると比較的早く処理が完了します。
■PHPのコード例
exec('nohup /usr/local/bin/php /virtual/[アカウント名]/cron1.php > /dev/null &');
exec('nohup /usr/local/bin/php /virtual/[アカウント名]/cron2.php > /dev/null &');
exec('nohup /usr/local/bin/php /virtual/[アカウント名]/cron3.php > /dev/null &');
ポイントは3つあって、
- コマンド末尾の &
- /dev/nullへのリダイレクト
- コマンドの最初につけている nohup
コマンドの末尾に & を付けた場合、バックグラウンドプロセスとして実行されるので、そのスクリプトの終了を待たず次のスクリプトが実行できます。
但し、cronの場合、ファイルディスクリプタが閉じない限り次を実行しないというルールがあるため、スクリプトの出力を /dev/null にしないと非同期実行できません。
(そのため、非同期で実行するスクリプトは自前でログを記録すべき)
これだけで良いように思われますが、exec関数を抜けてくるとプロセスがkillされちゃいますので、コマンドの頭に nohup を付けて、シェルから離れた後も実行を続けるようにします。
まとめ
せっかく学んだので備忘録的にnohupについて書いてみましたが、そこまで使う機会はないかもですね。
メンテナンス系の処理なら、人のいない時間帯にこっそり実行するでしょうし、出力ログも取っておきたいので普通に同期実行したほうが良さそうな気がします。
個人的にはRSSフィードの取得とか、多数のサイトへの同時接続をする処理があったので、その応答待ちをしている間に別のメンテナンスタスクを非同期実行しちゃお、という、わりとどうでもいい理由でnohupを使ってみたのでした。