著者は、 Write for DOnations プログラムの一環として、 Girls WhoCodeを選択して寄付を受け取りました。

序章

Linuxでは、用途の広い crontabツールを使用して、特定の時間にバックグラウンドで長時間実行されるタスクを処理できます。 デーモンは反復的なタスクを実行するのに最適ですが、1つの制限があります。タスクを実行できるのは、最小時間間隔1分のみです。

ただし、多くのアプリケーションでは、ユーザーエクスペリエンスの低下を防ぐために、ジョブをより頻繁に実行することをお勧めします。 たとえば、ジョブキューモデルを使用してWebサイトでファイル処理タスクをスケジュールしている場合、大幅な待機はエンドユーザーに悪影響を及ぼします。

もう1つのシナリオは、ジョブキューモデルを使用して、クライアントがアプリケーションで特定のタスク(たとえば、受信者に送金する)を完了した後、テキストメッセージまたは電子メールをクライアントに送信するアプリケーションです。 ユーザーが確認メッセージの配信まで1分待たなければならない場合、トランザクションが失敗したと考えて、同じトランザクションを繰り返そうとする可能性があります。

これらの課題を克服するために、crontabデーモンが1分後に再度呼び出すのを待つ間、タスクを60秒間繰り返しループして処理するPHPスクリプトをプログラムできます。 PHPスクリプトがcrontabデーモンによって初めて呼び出されると、ユーザーを待たせることなく、アプリケーションのロジックに一致する期間でタスクを実行できます。

このガイドでは、サンプルを作成します cron_jobs Ubuntu20.04サーバー上のデータベース。 次に、を設定します tasks テーブルと、PHPを使用して5秒間隔でテーブル内のジョブを実行するスクリプト while(...){...} ループと sleep() 機能。

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • root以外のユーザーでセットアップされたUbuntu20.04サーバー。 Ubuntu20.04を使用したサーバーの初期設定ガイドに従ってください。

  • サーバーにセットアップされたLAMPスタック。 Linux、Apache、MySQL、PHP(LAMP)スタックをUbuntu20.04にインストールする方法ガイドを参照してください。 このチュートリアルでは、ステップ4 —Webサイトの仮想ホストの作成をスキップできます。

ステップ1—データベースのセットアップ

このステップでは、サンプルデータベースとテーブルを作成します。 初め、 SSH サーバーにアクセスし、rootとしてMySQLにログインします。

  1. sudo mysql -u root -p

MySQLサーバーのrootパスワードを入力し、を押します ENTER 続行します。 次に、次のコマンドを実行して、 cron_jobs データベース。

  1. CREATE DATABASE cron_jobs;

データベースのroot以外のユーザーを作成します。 に接続するには、このユーザーの資格情報が必要です cron_jobs PHPからのデータベース。 交換することを忘れないでください EXAMPLE_PASSWORD 強い価値を持つ:

  1. CREATE USER 'cron_jobs_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
  2. GRANT ALL PRIVILEGES ON cron_jobs.* TO 'cron_jobs_user'@'localhost';
  3. FLUSH PRIVILEGES;

次に、に切り替えます cron_jobs データベース:

  1. USE cron_jobs;
Output
Database changed

データベースを選択したら、 tasks テーブル。 この表には、cronジョブによって自動的に実行されるいくつかのタスクを挿入します。 cronジョブを実行するための最小時間間隔は 1 後で、この設定をオーバーライドするPHPスクリプトをコーディングし、代わりに5秒間隔でジョブを実行します。

今のところ、 tasks テーブル:

  1. CREATE TABLE tasks (
  2. task_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  3. task_name VARCHAR(50),
  4. queued_at DATETIME,
  5. completed_at DATETIME,
  6. is_processed CHAR(1)
  7. ) ENGINE = InnoDB;

タスクテーブルに3つのレコードを挿入します。 MySQLを使用する NOW() の機能 queued_at タスクがキューに入れられた現在の日時を記録する列。 またのために completed_at 列、MySQLを使用 CURDATE() デフォルト時間を設定する関数 00:00:00. 後で、タスクが完了すると、スクリプトは次の列を更新します。

  1. INSERT INTO tasks (task_name, queued_at, completed_at, is_processed) VALUES ('TASK 1', NOW(), CURDATE(), 'N');
  2. INSERT INTO tasks (task_name, queued_at, completed_at, is_processed) VALUES ('TASK 2', NOW(), CURDATE(), 'N');
  3. INSERT INTO tasks (task_name, queued_at, completed_at, is_processed) VALUES ('TASK 3', NOW(), CURDATE(), 'N');

それぞれを実行した後、出力を確認します INSERT 指図:

Output
Query OK, 1 row affected (0.00 sec) ...

を実行して、データが適切に配置されていることを確認します SELECT に対する声明 tasks テーブル:

  1. SELECT task_id, task_name, queued_at, completed_at, is_processed FROM tasks;

すべてのタスクのリストがあります。

Output
+---------+-----------+---------------------+---------------------+--------------+ | task_id | task_name | queued_at | completed_at | is_processed | +---------+-----------+---------------------+---------------------+--------------+ | 1 | TASK 1 | 2021-03-06 06:27:19 | 2021-03-06 00:00:00 | N | | 2 | TASK 2 | 2021-03-06 06:27:28 | 2021-03-06 00:00:00 | N | | 3 | TASK 3 | 2021-03-06 06:27:36 | 2021-03-06 00:00:00 | N | +---------+-----------+---------------------+---------------------+--------------+ 3 rows in set (0.00 sec)

の時間 completed_at 列がに設定されている 00:00:00、次に作成するPHPスクリプトによってタスクが処理されると、この列が更新されます。

MySQLコマンドラインインターフェイスを終了します。

  1. QUIT;
Output
Bye

君の cron_jobs データベースと tasks これでテーブルが配置され、ジョブを処理するPHPスクリプトを作成できるようになりました。

ステップ2—5秒後にタスクを実行するPHPスクリプトを作成する

このステップでは、PHPの組み合わせを使用するスクリプトを作成します while(...){...} ループと sleep 5秒ごとにタスクを実行する機能。

新しいを開く /var/www/html/tasks.php nanoを使用してWebサーバーのルートディレクトリにあるファイル:

  1. sudo nano /var/www/html/tasks.php

次に、新しいを作成します try { 後のブロック <?php 手順1で作成したデータベース変数にタグを付けて宣言します。 交換することを忘れないでください EXAMPLE_PASSWORD データベースユーザーの実際のパスワードを使用して:

/var/www/html/tasks.php
<?php
try {
    $db_name     = 'cron_jobs';
    $db_user     = 'cron_jobs_user';
    $db_password = 'EXAMPLE_PASSWORD';
    $db_host     = 'localhost';

次に、新しいPDO(PHPデータオブジェクト)クラスを宣言し、属性を設定します ERRMODE_EXCEPTION PDOエラーをキャッチします。 また、切り替えます ATTR_EMULATE_PREPARESfalse ネイティブMySQLデータベースエンジンにエミュレーションを処理させるため。 プリペアドステートメントを使用すると、SQLクエリとデータを別々に送信して、セキュリティを強化し、SQLインジェクション攻撃の可能性を減らすことができます。

/var/www/html/tasks.php

    $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);       

次に、という名前の新しい変数を作成します $loop_expiry_time 現在の時刻に60秒を加えた値に設定します。 次に、新しいPHPを開きます while(time() < $loop_expiry_time) { 声明。 ここでの考え方は、現在の時刻まで実行されるループを作成することです(time())変数に一致します $loop_expiry_time:

[label /var/www/html/tasks.php]       

    $loop_expiry_time = time() + 60;

    while (time() < $loop_expiry_time) { 

次に、未処理のジョブをから取得する準備済みのSQLステートメントを宣言します。 tasks テーブル:

[label /var/www/html/tasks.php]   

        $data = [];
        $sql  = "select 
                 task_id
                 from tasks
                 where is_processed = :is_processed
                 ";

SQLコマンドを実行し、からすべての行をフェッチします。 tasks 列を持つテーブル is_processed に設定 N. これは、行が処理されないことを意味します。

[label /var/www/html/tasks.php]  

        $data['is_processed'] = 'N';  

        $stmt = $pdo->prepare($sql);
        $stmt->execute($data);

次に、PHPを使用して取得した行をループします while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {...} ステートメントを作成し、別のSQLステートメントを作成します。 今回は、SQLコマンドが更新します is_processedcompleted_at 処理された各タスクの列。 これにより、タスクを複数回処理しないようになります。

[label /var/www/html/tasks.php]  

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 
            $data_update   = [];         
            $sql_update    = "update tasks set 
                              is_processed  = :is_processed,
                              completed_at  = :completed_at
                              where task_id = :task_id                                 
                              ";

            $data_update   = [		          
                             'is_processed' => 'Y',                          
                             'completed_at' => date("Y-m-d H:i:s"),
                             'task_id'      => $row['task_id']                         
                             ];
            $stmt = $pdo->prepare($sql_update);
            $stmt->execute($data_update);
        }

注:処理するキューが大きい場合(たとえば、1秒あたり100,000レコード)、MySQLよりも高速であるためRedisサーバーでジョブをキューに入れることを検討してください。ジョブキューモデルの実装になります。 それでも、このガイドではより小さなデータセットを処理します。

最初のPHPを閉じる前に while (time() < $loop_expiry_time) { ステートメント、含める sleep(5); ジョブの実行を5秒間一時停止し、サーバーリソースを解放するステートメント。

ビジネスロジックとタスクの実行速度に応じて、5秒の期間を変更できます。 たとえば、タスクを1分間に3回処理する場合は、この値を20秒に設定します。

忘れないでください catch 内部のPDOエラーメッセージ } catch (PDOException $ex) { echo $ex->getMessage(); } ブロック:

[label /var/www/html/tasks.php]         
        sleep(5); 

        }       
		
} catch (PDOException $ex) {
    echo $ex->getMessage(); 
}

あなたの完全な tasks.php ファイルは次のようになります。

/var/www/html/tasks.php
<?php
try {
    $db_name     = 'cron_jobs';
    $db_user     = 'cron_jobs_user';
    $db_password = 'EXAMPLE_PASSWORD';
    $db_host     = 'localhost';

    $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);               

    $loop_expiry_time = time() + 60;

    while (time() < $loop_expiry_time) { 
        $data = [];
        $sql  = "select 
                 task_id
                 from tasks
                 where is_processed = :is_processed
                 ";

        $data['is_processed'] = 'N';             

        $stmt = $pdo->prepare($sql);
        $stmt->execute($data);

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 
            $data_update   = [];         
            $sql_update    = "update tasks set 
                              is_processed  = :is_processed,
                              completed_at  = :completed_at
                              where task_id = :task_id                                 
                              ";

            $data_update   = [		          
                             'is_processed' => 'Y',                          
                             'completed_at' => date("Y-m-d H:i:s"),
                             'task_id'      => $row['task_id']                         
                             ];
            $stmt = $pdo->prepare($sql_update);
            $stmt->execute($data_update);
        }   
       
        sleep(5); 

        }       
		
} catch (PDOException $ex) {
    echo $ex->getMessage(); 
}

を押してファイルを保存します CTRL + X, Y それから ENTER.

ロジックのコーディングが完了したら、 /var/www/html/tasks.php ファイルの場合、次のステップで1分ごとにファイルを実行するようにcrontabデーモンをスケジュールします。

ステップ3—1分後に実行するPHPスクリプトのスケジュール

Linuxでは、crontabファイルにコマンドを入力することにより、規定の時間後にジョブが自動的に実行されるようにスケジュールできます。 このステップでは、crontabデーモンに実行するように指示します /var/www/html/tasks.php 毎分スクリプト。 だから、 /etc/crontab nanoを使用したファイル:

  1. sudo nano /etc/crontab

次に、ファイルの最後に以下を追加して、 http://localhost/tasks.php 毎回 1 分:

/ etc / crontab
...
* * * * * root /usr/bin/wget -O - http://localhost/tasks.php

ファイルを保存して閉じます。

このガイドは、cronジョブがどのように機能するかについての基本的な知識があることを前提としています。 Cronを使用してUbuntuでタスクを自動化する方法に関するガイドを読むことを検討してください。

前に示したように、cronデーモンは tasks.php 1分ごとにファイルを作成します。ファイルが初めて実行されると、開いているタスクをさらに60秒間ループします。 ループ時間が経過するまでに、cronデーモンがファイルを再度実行し、プロセスが続行されます。

更新して閉じた後 /etc/crontab ファイルの場合、crontabデーモンは、に挿入したMySQLタスクの実行を開始する必要があります。 tasks すぐにテーブル。 すべてが期待どおりに機能しているかどうかを確認するには、 cron_jobs 次のデータベース。

ステップ4—ジョブの実行を確認する

このステップでは、データベースをもう一度開いて、データベースが tasks.php crontabによって自動的に実行されると、ファイルはキューに入れられたジョブを処理しています。

rootとしてMySQLサーバーに再度ログインします。

  1. sudo mysql -u root -p

次に、MySQLサーバーのルートパスワードを入力して、 ENTER 続行します。 次に、データベースに切り替えます。

  1. USE cron_jobs;
Output
Database changed

を実行します SELECT に対する声明 tasks テーブル:

SELECT task_id, task_name, queued_at, completed_at, is_processed FROM tasks;

次のような出力が表示されます。 の中に completed_at 列、タスクは次の間隔で処理されています 5 秒。 また、タスクは、 is_processed 列がに設定されました Y、つまり YES.

Output
+---------+-----------+---------------------+---------------------+--------------+ | task_id | task_name | queued_at | completed_at | is_processed | +---------+-----------+---------------------+---------------------+--------------+ | 1 | TASK 1 | 2021-03-06 06:27:19 | 2021-03-06 06:30:01 | Y | | 2 | TASK 2 | 2021-03-06 06:27:28 | 2021-03-06 06:30:06 | Y | | 3 | TASK 3 | 2021-03-06 06:27:36 | 2021-03-06 06:30:11 | Y | +---------+-----------+---------------------+---------------------+--------------+ 3 rows in set (0.00 sec)

これにより、PHPスクリプトが期待どおりに機能していることが確認されます。 crontabデーモンによって設定された1分間の制限をオーバーライドすることにより、より短い時間間隔でタスクを実行しました。

結論

このガイドでは、Ubuntu20.04サーバーにサンプルデータベースをセットアップしました。 次に、テーブルにジョブを作成し、PHPを使用して5秒間隔で実行します。 while(...){...} ループと sleep() 機能。 次に、タスクを1分間に複数回実行する必要があるジョブキューベースのアプリケーションを実装する場合は、このチュートリアルのロジックを使用してください。

その他のPHPチュートリアルについては、PHPトピックページをご覧ください。