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()); } public static function delete( $id ) { $db = Mailouts::get_db(); $sth = $db->prepare( 'DELETE FROM mailouts WHERE id=?' ); $db->execute( $sth, $id ); return true; } public static function add( $from, $to, $subject, $content ) { $db = Mailouts::get_db(); $db->prepare( 'INSERT INTO メールアウトVALUES (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 つの主要な静的関数が含まれます。 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 のメールを使用します。 () メソッドはメッセージを 1 つずつ送信します。電子メールが正常に送信されるたびに、delete() メソッドが呼び出され、対応するレコードがキューから削除されます。
cron デーモンを使用して、このスクリプトを定期的に実行します。このスクリプトを実行する頻度は、アプリケーションのニーズによって異なります。
注: PHP 拡張機能およびアプリケーション リポジトリ (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() が使用されます。
清单 7 に示されたテスト スクリプトは、処理リストに 1 つの項目を追加します。 ::add( 'printvalue', array( 'value' => '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 Printing: 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;}?
BatchFiles には、ADD ()、get_all ()、および delete () という 3 つの主要なメソッドがあります。このクラスはデータベースにアクセスしませんが、batch_items ディレクトリ内のファイルの読み取りと書き込みを行います。
次のテスト コードを使用して、新しいバッチ エントリを追加します。
リスト 10.batch_by_file_test_add.php
'foo' ) );?> ;
注意すべき点が 1 つあります。クラス名 (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 以上の記事の著者です。