Web アプリケーションの機能が完了するまでに 1 ~ 2 秒以上かかる場合はどうすればよいでしょうか?何らかのオフライン処理ソリューションが必要です。 PHP アプリケーションで長時間実行ジョブをオフラインで処理するいくつかの方法を学びます。
大手チェーン店は大きな問題を抱えている。毎日、あらゆる店舗で何千もの取引が発生します。会社の幹部はこのデータをマイニングしたいと考えています。どの商品がよく売れているのか?何がダメなの?オーガニック製品はどこでよく売れますか?アイスクリームの売れ行きはどうですか?
このデータを取得するには、組織はすべてのトランザクション データを、企業が必要とする種類のレポートの生成により適したデータ モデルに読み込む必要があります。ただし、これには時間がかかり、チェーンが成長するにつれて、1 日分のデータを処理するのに 1 日以上かかる場合があります。したがって、これは大きな問題です。
さて、Web アプリケーションはこれほど大量のデータを処理する必要はないかもしれませんが、どのサイトでも処理に顧客が期待する以上に時間がかかる可能性があります。一般に、顧客が待ち時間を許容できる時間は 200 ミリ秒です。この時間を超えると、顧客はプロセスが「遅い」と感じます。この数字はデスクトップ アプリケーションに基づいていますが、Web のおかげでより忍耐強くなります。しかし、何があっても、顧客を数秒以上待たせてはいけません。したがって、PHP でバッチ ジョブを処理するには、いくつかの戦略を採用する必要があります。
cron による分散型アプローチ
UNIX® マシンでは、バッチ処理を実行するコア プログラムは cron デーモンです。デーモンは、どのコマンド ラインをどのくらいの頻度で実行するかを指示する構成ファイルを読み取ります。その後、デーモンは設定に従ってそれらを実行します。エラーが発生した場合、問題のデバッグに役立つように、指定された電子メール アドレスにエラー出力を送信することもできます。
私はスレッド技術の使用を強く主張するエンジニアを何人か知っています。 「スレッドです! スレッドはバックグラウンド処理を行うための本当の方法です。cron デーモンはとても時代遅れです
私はそうは思いません。」
私は両方の方法を使用しましたが、cron には「Keep It Simple, Stupid (KISS、シンプルは美しい)」という原則の利点があると思います。バックグラウンド処理をシンプルに保ちます。常に実行される (メモリ リークがないように) マルチスレッドのジョブ処理アプリケーションを作成する代わりに、cron は単純なバッチ スクリプトを開始します。このスクリプトは、処理するジョブがあるかどうかを判断し、ジョブを実行して終了します。メモリリークを心配する必要はありません。また、スレッドが停止したり、無限ループに陥ったりすることを心配する必要もありません。
では、cron はどのように機能するのでしょうか?これはシステム環境によって異なります。ここでは古い単純な UNIX コマンド ライン バージョンの cron についてのみ説明します。これを独自の Web アプリケーションに実装する方法については、システム管理者に問い合わせてください。
これは、毎晩午後 11 時に PHP スクリプトを実行する単純な cron 設定です:
0 23 * * * jack /usr/bin/php /users/home/jack/myscript.php
上位 5 このフィールドは次の内容を定義しますスクリプトを開始するタイミング。次に、このスクリプトの実行に使用するユーザー名を指定します。残りのコマンドは、実行されるコマンド ラインです。時間フィールドは、分、時間、日、月、曜日です。以下にいくつかの例を示します。
コマンド:
15 * * * * jack /usr/bin/php /users/home/jack/myscript.php
毎時15分にスクリプトを実行します。
コマンド:
15,45 * * * * jack /usr/bin/php /users/home/jack/myscript.php
毎時15分と45分にスクリプトを実行します。
コマンド:
*/1 3-23 * * * jack /usr/bin/php /users/home/jack/myscript.php
午前 3 時から午後 11 時までの間、毎分スクリプトを実行します。
コマンド
30 23 * * 6 jack /usr/bin/php /users/home/jack/myscript.php
毎週土曜日午後 11 時 30 分にスクリプトを実行します (土曜日は 6 で指定されます)。
ご覧のとおり、組み合わせの数は無制限です。必要に応じて、スクリプトをいつ実行するかを制御できます。実行する複数のスクリプトを指定して、一部のスクリプトを毎分実行し、他のスクリプト (バックアップ スクリプトなど) を 1 日に 1 回だけ実行することもできます。
報告されたエラーの送信先電子メール アドレスを指定するには、次のように MAILTO ディレクティブを使用できます:
MAILTO=jherr@pobox.com
注: Microsoft® Windows® ユーザーの場合は、同等のスケジュールされたタスクがあります。システムを使用して、コマンド ライン プロセス (PHP スクリプトなど) を定期的に開始できます。
トップに戻る
バッチ処理アーキテクチャの基本
バッチ処理は非常に単純です。ほとんどの場合、2 つのワークフローのいずれかが使用されます。最初のワークフローはレポート用です。スクリプトは 1 日に 1 回実行され、レポートを生成してユーザーのグループに送信します。 2 番目のワークフローは、何らかのリクエストに応じて作成されるバッチ ジョブです。たとえば、Web アプリケーションにログインし、システムに登録されているすべてのユーザーに新しい機能について知らせるメッセージを送信するように依頼しました。システムには 10,000 人のユーザーが存在するため、この操作はバッチ処理する必要があります。 PHP はこのようなタスクを完了するのに時間がかかるため、ブラウザの外部のジョブによって実行する必要があります。
2 番目のワークフローでは、Web アプリケーションは単に情報をどこかに置き、バッチ アプリケーションにそれを共有させます。これらのメッセージは、ジョブの性質を指定します (たとえば、「この電子メールをシステム上のすべての人に送信する」など)。) バッチ プログラムはジョブを実行し、その後ジョブを削除します。あるいは、ハンドラーはジョブを完了としてマークします。使用する方法に関係なく、ジョブは完了したと認識され、再実行されないようにする必要があります。
この記事の残りの部分では、Web アプリケーションのフロントエンドとバッチ バックエンドの間でデータを共有するさまざまな方法を説明します。
トップに戻る
メールキュー
最初の方法は、専用のメールキューシステムを使用することです。このモデルでは、データベース内のテーブルに、さまざまなユーザーに送信する必要がある電子メール メッセージが含まれています。 Web インターフェイスは、mailouts クラスを使用して電子メールをキューに追加します。電子メール ハンドラーは、mailout クラスを使用して未処理の電子メールを取得し、それを再度使用して未処理の電子メールをキューから削除します。
このモデルにはまず MySQL スキーマが必要です。
リスト 1. mailout.sql
DROP TABLE IF EXISTS mailouts;CREATE TABLE mailouts ( id MEDIUMINT NOT NULL AUTO_INCREMENT, from_address TEXT NOT NULL, to_address TEXT NOT NULL, subject TEXT NOT NULL, content TEXT NOT NULL, PRIMARY KEY (id ) );
このモードは非常にシンプルです。各行には、電子メールの件名と内容だけでなく、差出人アドレスと宛先アドレスも含まれます。
PHP メールアウト クラスは、データベース内のメールアウト テーブルを処理するために使用されます。
リスト 2. mailouts.php
getMessage()) } return $db; id ) { $db = メールアウト::get_db(); $sth = $db->prepare( 'DELETE FROM mailouts WHERE id=?' ); $db->execute( $sth, $id ); ; } public static function add( $from, $to, $subject, $content ) { $db = Mailouts::get_db(); $sth = $db->prepare( 'メールアウト値に挿入 (null,?, ?,?,?)' ); $db->execute( $sth, array( $from, $to, $subject, $content ) ); } public static function get_all() { $db = メールアウト::get_db(); $res = $db->query( "SELECT * FROM メールアウト" ); $rows = array(); while( $res->fetchInto( $row ) ) { $rows []= $row; } return $rows; }}?> このスクリプトには Pear::DB データベース アクセス クラスが含まれています。次に、add、delete、get_all という 3 つの主要な静的関数を含む mailouts クラスを定義します。 add() メソッドは電子メールをキューに追加します。このメソッドはフロントエンドによって使用されます。 get_all() メソッドは、テーブルからすべてのデータを返します。 delete() メソッドは電子メールを削除します。
なぜスクリプトの最後で delete_all() メソッドを呼び出さないのかと疑問に思われるかもしれません。これを行わない理由は 2 つあります。送信後に各メッセージを削除すると、問題が発生した後でスクリプトが再実行されたとしても、メッセージが 2 回送信される可能性は低くなります。バッチジョブ情報の完了。
次のステップは、キューにエントリを追加する簡単なテスト スクリプトを作成することです。
リスト 3. mailout_test_add.php
この例では、件名「テスト件名」とメール本文を含むメールアウトを会社の Molly に送信します。このスクリプトはコマンド ラインで実行できます: php mailout_test_add.php。
電子メールを送信するには、別のスクリプトが必要です。このスクリプトはジョブ ハンドラーとして機能します。
リスト 4. mailout_send.php
このスクリプトは、get_all() メソッドを使用してすべての電子メール メッセージを取得し、PHP の mail() メソッドを使用してメッセージを 1 つずつ送信します。電子メールが正常に送信されるたびに、delete() メソッドが呼び出され、対応するレコードがキューから削除されます。
このスクリプトを定期的に実行するには、cron デーモンを使用します。このスクリプトを実行する頻度は、アプリケーションのニーズによって異なります。
注: PHP Extension and Application Repository (PEAR) リポジトリには、無料でダウンロードできる優れたメール キュー システム実装が含まれています。
トップに戻る
より一般的なアプローチ
電子メールを送信するための特殊なソリューションは優れていますが、より一般的なアプローチはありますか?処理が完了するまでブラウザで待つことなく、電子メールの送信、レポートの生成、またはその他の時間のかかる処理を実行できる必要があります。
このために、PHP がインタープリタ型言語であるという事実を利用できます。 PHP コードはデータベース内のキューに保存し、後で実行できます。これには 2 つのテーブルが必要です (リスト 5 を参照)。
リスト5. generic.sql
DROP TABLE IF EXISTS process_items;CREATE TABLE process_items (id MEDIUMINT NOT NULL AUTO_INCREMENT, function TEXT NOT NULL, PRIMARY KEY (id));DROP TABLE IF EXISTS process_args;CREATE TABLE process_args ( id MEDIUMINT NOT NULL AUTO_INCREMENT、item_id MEDIUMINT NOT NULL、key_name TEXT NOT NULL、value TEXT NOT NULL、PRIMARY KEY (id));
最初のテーブル process_items には、ジョブ ハンドラーによって呼び出される関数が含まれています。 2 番目のテーブル process_args には、関数に送信される引数がキーと値のペアのハッシュ テーブルの形式で含まれています。
メールアウト テーブルと同様に、これら 2 つのテーブルも ProcessingItems と呼ばれる PHP クラスによってラップされています。
清单 6. generic.php
prepare( 'DELETE FROM processing_args WHERE item_id=?' ); $db->execute( $sth, $id ); $sth = $db->prepare( 'DELETE FROM processing_items WHERE id=?' ); $db->execute( $sth, $id ); true を返します。 } public static function add( $function, $args ) { $db = ProcessingItems::get_db(); $sth = $db->prepare( 'INSERT INTO processing_items VALUES (null,?)' ); $db->execute( $sth, array( $function ) ); $res = $db->query( "SELECT last_insert_id()" ); $id = null; while( $res->fetchInto( $row ) ) { $id = $row[0]; } foreach( $args as $key => $value ) { $sth = $db->prepare( 'INSERT INTO processing_args VALUES (null,?,?,?)' ); $db->execute( $sth, array( $id, $key, $value ) ); } trueを返します。 } パブリック 静的 関数 get_all() { $db = ProcessingItems::get_db(); $res = $db->query( "SELECT * FROM processing_items" ); $rows = array(); while( $res->fetchInto( $row ) ) { $item = array(); $item['id'] = $row[0]; $item['関数'] = $row[1]; $item['args'] = array(); $ares = $db->query( "SELECT key_name, value FROM processing_args WHERE item_id=?", $item['id'] ); while( $ares->fetchInto( $arow ) ) $item['args'][ $arow[0] ] = $arow[1]; $rows []= $item; } $rows を返します。 }}?>
このクラスには、add()、get_all()、delete() という 3 つの重要なメソッドが含まれています。メールアウト システムと同様に、フロントエンドでは add() が使用され、処理ガイドでは get_all() と delete() が使用されます。示されているテスト スクリプトは、処理リストに 1 つの項目を追加します。 'foo' ) );?>この例では、printvalue 関数に対する調整が追加され、value パラメータが foo に設定されています。私は PHP コマンド行アンパイラを使用してこれを実行し、このメソッドを次の処理で使用するように調整しています。
清单 8. generic_process.php
このスクリプトは非常に単純です。 get_all() で返される処理項目を取得し、次に call_user_func_array (PHP の内部関数の 1 つ) を使用して、このメソッドの使用を規定のパラメータで制御します。この例では、ローカルの printvalue 関数が呼び出されます。
この機能を実証するために、コマンドラインで何が起こるかを見てみましょう:
% php generic_test_add.php % php generic_process.php 印刷: foo%
出力はそれほど多くありませんが、要点はわかります。このメカニズムにより、PHP 関数の処理を延期できます。
さて、PHP 関数の名前とパラメーターをデータベースに入れるのが好きではない場合は、データベース内の「処理ジョブ タイプ」名と実際の PHP 処理関数の間のマッピングを PHP コード内で確立するという別の方法もあります。こうすることで、後で PHP バックエンドを変更することにした場合でも、「処理ジョブ タイプ」文字列が一致する限り、システムは引き続き動作します。
トップに戻る
データベースを捨てる
最後に、データベースを使用する代わりに、ディレクトリ内のファイルを使用してバッチ ジョブを保存する、少し異なるソリューションを示します。ここで示したアイデアは、「データベースを使用する代わりにこの方法を採用する」ことを提案するものではありません。これは単なる代替方法であり、それを採用するかどうかを決定するのはあなた次第です。
データベースを使用していないため、このソリューションにはスキーマがありません。したがって、最初に、前の例と同様に、add()、get_all()、および delete() メソッドを含むクラスを作成します。
清单 9. batch_by_file.php
$v ) { fprintf( $fh, $k.":".$v."n" ); } fclose( $fh ); true を返します。 } パブリック 静的 関数 get_all() { $rows = array(); if (is_dir(BATCH_DIRECTORY)) { if ($dh = opendir(BATCH_DIRECTORY)) { while (($file = readdir($dh)) !== false) { $path = BATCH_DIRECTORY.$file; if ( is_dir( $path ) == false ) { $item = array(); $item['id'] = $path; $fh = fopen( $path, 'r' ); if ( $fh ) { $item['function'] = trim(fgets( $fh )); $item['args'] = array(); while(( $line = fgets( $fh ) ) != null ) { $args = split( ':', trim($line) ); $item['args'][$args[0]] = $args[1]; } $rows []= $item; fclose( $fh ); } } }Closedir ($ dh);}} Return $ rows;}} & Gt;
BatchFiles には、add ()、get_all ()、delete () の 3 つの主要なメソッドがあります。このクラスはデータベースにアクセスしませんが、batch_items ディレクトリ内のファイルの読み取りと書き込みを行います。
次のテストコードを使用して、新しいバッチエントリを追加します。
リスト 10.batch_by_file_test_add.php
'foo' ) );?>注: 実際には、クラス名 (BatchFiles) 以外にジョブがどのように保存されるかを示すものはありません。したがって、将来的にはインターフェイスを変更することなく、データベース スタイルのストレージに簡単に変更できます。
最後にハンドラーコードです。
リスト11.batch_by_file_processor.php
;
このコードは、ファイル名とクラス名が変更されていることを除いて、データベースのバージョンとほぼ同じです。
上に戻る
結論
前述したように、サーバーはスレッドに多くのサポートを提供し、バックグラウンドでバッチ処理を実行できます。場合によっては、小さなジョブを処理するためにワーカー スレッドを使用した方が明らかに簡単です。ただし、バッチ ジョブは、実装、展開、保守が簡単な従来のツール (cron、MySQL、標準オブジェクト指向 PHP、および Pear::DB) を使用して PHP アプリケーションで作成することもできます。
参考文献
学習
この記事の英語の原文は、developerWorks グローバル サイトで参照できます。
PHP について詳しくは、IBM developerWorks の PHP プロジェクト リソース センターをお読みください。
PHP.net は、PHP 開発者にとって優れたリソースです。
PEAR Mail_Queue パッケージは、データベース バックエンドを含む堅牢なメール キュー実装です。
crontab のマニュアルには cron 設定の詳細が記載されていますが、理解するのは簡単ではありません。
PHP マニュアルのコマンドラインからの PHP の使用に関するセクションは、cron からスクリプトを実行する方法を理解するのに役立ちます。
developerWorks の技術イベントや Web キャストにご注目ください。
IBM オープンソース開発者が最新のテクノロジー開発について学ぶことができる、世界中で今後開催されるカンファレンス、展示会、Web キャスト、その他のイベントについて学びます。
developerWorks オープンソース・テクノロジー・ゾーンにアクセスして、オープンソース・テクノロジーを使用した開発や IBM 製品での使用に役立つ広範なハウツー情報、ツール、プロジェクトの最新情報を入手してください。
developerWorks ポッドキャストには、ソフトウェア開発者向けの興味深いインタビューやディスカッションがたくさん含まれています。
製品とテクノロジーを入手する
PEAR -- PHP 拡張機能およびアプリケーション リポジトリ (Pear::DB を含む) をチェックしてください。
ダウンロードまたは DVD で入手できる IBM 試用版ソフトウェアを使用して、次のオープンソース開発プロジェクトを改善します。
ディスカッション
developerWorks PHP 開発者フォーラムは、すべての PHP 開発者が技術的な問題について話し合う場所を提供します。 PHP スクリプト、関数、構文、変数、デバッグ、その他のトピックについて質問がある場合は、ここで質問できます。
developerWorks ブログに参加して、developerWorks コミュニティに参加してください。
著者について
Jack D. Herrington は、20 年以上の実務経験を持つシニア ソフトウェア エンジニアです。彼は、『Code Generation in Action』、『Podcasting Hacks』、および『PHP Hacks』という 3 冊の書籍と 30 以上の記事の著者です。