如何在 Ubuntu 20.04 上使用 Crontab 在一分钟内多次运行 PHP 作业

作者选择了 Girls Who Code以作为 Write for Donations计划的一部分获得捐款。

介绍

在 Linux 中,你可以使用多功能的 crontab 工具来在特定时间在背景中处理长时间的任务.虽然 DAEMON 非常适合执行重复性的任务,但它有一个局限性:你只能在最小 1 分钟的时间间隔下执行任务。

但是,在许多应用程序中,为了避免用户体验不佳,最好让任务更频繁地执行,例如,如果您使用 job-queue 模型在您的网站上计划文件处理任务,那么显著的等待会对最终用户产生负面影响。

另一个场景是应用程序使用工作队列模型向客户发送短信或电子邮件,一旦他们在应用程序中完成了一项特定任务(例如,向收件人发送资金)。

要克服这些挑战,你可以编程一个PHP脚本,它循环和处理任务反复60秒,因为它等待Crontab Daemon在一分钟后再次调用它。

在本指南中,您将在 Ubuntu 20.04 服务器上创建一个样本 cron_jobs 数据库,然后,您将设置一个 tasks 表和一个脚本,使用 PHP while(...){...} 循环和 sleep() 函数以 5 秒的间隔执行您的表中的任务。

前提条件

要完成本教程,您需要以下内容:

步骤 1 - 创建数据库

在此步骤中,您将创建一个样本数据库和表. 首先,‘SSH’到您的服务器并登录到MySQL作为 root:

1sudo mysql -u root -p

输入 MySQL 服务器的 root 密码,然后按ENTER,然后运行以下命令来创建一个cron_jobs数据库。

1CREATE DATABASE cron_jobs;

创建数据库的非根用户. 您将需要该用户的身份证来连接到 PHP 的 cron_jobs 数据库. 请记住用一个强大的值代替 EXAMPLE_PASSWORD:

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

接下来,切换到cron_jobs数据库:

1USE cron_jobs;
1[secondary_label Output]
2Database changed

一旦你选择了数据库,创建一个任务表. 在这个表中,你将插入一些任务,将被自动执行的Cron任务. 由于运行Cron任务的最小时间间隔是1分钟,你将后来编码一个PHP脚本,这取代了这个设置,而不是,执行任务在5秒间隔。

现在,创建你的任务表:

1CREATE 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;

将三个记录插入到任务表中。在queued_at列中使用MySQLNOW()函数来记录任务排列的当前日期和时间。对于completed_at列,使用MySQLCURDATE()函数将默认时间设置为00:00:00

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

运行每个INSERT命令后,确认输出:

1[secondary_label Output]
2Query OK, 1 row affected (0.00 sec)
3...

通过对任务表运行一个SELECT陈述来确保数据处于位置:

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

您将找到所有任务的列表:

1[secondary_label Output]
2+---------+-----------+---------------------+---------------------+--------------+
3| task_id | task_name | queued_at           | completed_at        | is_processed |
4+---------+-----------+---------------------+---------------------+--------------+
5|       1 | TASK 1    | 2021-03-06 06:27:19 | 2021-03-06 00:00:00 | N            |
6|       2 | TASK 2    | 2021-03-06 06:27:28 | 2021-03-06 00:00:00 | N            |
7|       3 | TASK 3    | 2021-03-06 06:27:36 | 2021-03-06 00:00:00 | N            |
8+---------+-----------+---------------------+---------------------+--------------+
93 rows in set (0.00 sec)

「完成_at」列的時間設定為「00:00:00」,此列會在您下次創建的PHP脚本處理任務後更新。

退出 MySQL 命令行接口:

1QUIT;
1[secondary_label Output]
2Bye

您的cron_jobs数据库和tasks表现在已经到位,您现在可以创建一个处理任务的PHP脚本。

步骤 2 — 创建一个 PHP 脚本,在 5 秒内运行任务

在此步骤中,您将创建一个脚本,该脚本使用PHPwhile(...){...}循环和sleep函数的组合,每5秒执行任务。

在您的网页服务器的 root 目录中使用 nano 打开新的 /var/www/html/tasks.php 文件:

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

接下来,在一个<?php标签后创建一个新的试用块,并声明您在步骤 1 中创建的数据库变量,请记住用数据库用户的实际密码代替EXAMPLE_PASSWORD:

1[label /var/www/html/tasks.php]
2<?php
3try {
4    $db_name     = 'cron_jobs';
5    $db_user     = 'cron_jobs_user';
6    $db_password = 'EXAMPLE_PASSWORD';
7    $db_host     = 'localhost';

接下来,声明一个新的PDO(PHP数据对象)类,并设置属性ERRMODE_EXCEPTION,以捕捉任何PDO错误。 此外,切换ATTR_EMULATE_PREPARESfalse,让原生MySQL数据库引擎处理模拟。 准备的陈述允许您单独发送SQL查询和数据,以提高安全性并减少SQL注入攻击的可能性:

1[label /var/www/html/tasks.php]
2
3    $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
4    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
5    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

然后,创建一个名为$loop_expiry_time的新变量,并将其设置为当前时间加60秒,然后打开一个新的PHPwhile(time() < $loop_expiry_time) {陈述,这里的想法是创建一个循环,直到当前时间(time())匹配变量$loop_expiry_time:

1[label /var/www/html/tasks.php]       
2
3    $loop_expiry_time = time() + 60;
4
5    while (time() < $loop_expiry_time) {

接下来,声明准备的 SQL 语句,从任务表中获取未处理的工作:

1[label /var/www/html/tasks.php]   
2
3        $data = [];
4        $sql  = "select 
5                 task_id
6                 from tasks
7                 where is_processed = :is_processed
8                 ";

运行 SQL 命令并从具有is_processed列设置为N任务表中获取所有行,这意味着行不会被处理:

1[label /var/www/html/tasks.php]  
2
3        $data['is_processed'] = 'N';  
4
5        $stmt = $pdo->prepare($sql);
6        $stmt->execute($data);

接下来,循环使用一个 PHP while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {...} 语句,并创建另一个 SQL 语句。

 1[label /var/www/html/tasks.php]  
 2
 3        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 
 4            $data_update   = [];         
 5            $sql_update    = "update tasks set 
 6                              is_processed  = :is_processed,
 7                              completed_at  = :completed_at
 8                              where task_id = :task_id                                 
 9                              ";
10
11            $data_update   = [		          
12                             'is_processed' => 'Y',                          
13                             'completed_at' => date("Y-m-d H:i:s"),
14                             'task_id'      => $row['task_id']                         
15                             ];
16            $stmt = $pdo->prepare($sql_update);
17            $stmt->execute($data_update);
18        }

<$>[注] 注: 如果您需要处理大量的队列(例如,每秒100,000个记录),您可能会考虑在Redis Server(https://andsky.com/tags/redis)中排队工作,因为在执行任务队列模型时,它比MySQL更快。

在关闭第一个 PHP 语句while (time() < $loop_expiry_time) {之前,请包括一个sleep(5);语句来暂停工作执行 5 秒,并释放您的服务器资源。

您可以根据您的业务逻辑和您想要执行任务的速度更改 5 秒期,例如,如果您希望任务在一分钟内处理 3 次,请将此值设置为 20 秒。

请记住在捕获(PDOException $ex) { echo $ex->getMessage(); }` 区块内捕获任何 PDO 错误消息:

1[label /var/www/html/tasks.php]         
2        sleep(5); 
3
4        }       
5    	
6} catch (PDOException $ex) {
7    echo $ex->getMessage(); 
8}

您的完整的 tasks.php 文件将如下:

 1[label /var/www/html/tasks.php]
 2<?php
 3try {
 4    $db_name     = 'cron_jobs';
 5    $db_user     = 'cron_jobs_user';
 6    $db_password = 'EXAMPLE_PASSWORD';
 7    $db_host     = 'localhost';
 8
 9    $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
10    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
11    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);               
12
13    $loop_expiry_time = time() + 60;
14
15    while (time() < $loop_expiry_time) { 
16        $data = [];
17        $sql  = "select 
18                 task_id
19                 from tasks
20                 where is_processed = :is_processed
21                 ";
22
23        $data['is_processed'] = 'N';             
24
25        $stmt = $pdo->prepare($sql);
26        $stmt->execute($data);
27
28        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 
29            $data_update   = [];         
30            $sql_update    = "update tasks set 
31                              is_processed  = :is_processed,
32                              completed_at  = :completed_at
33                              where task_id = :task_id                                 
34                              ";
35
36            $data_update   = [		          
37                             'is_processed' => 'Y',                          
38                             'completed_at' => date("Y-m-d H:i:s"),
39                             'task_id'      => $row['task_id']                         
40                             ];
41            $stmt = $pdo->prepare($sql_update);
42            $stmt->execute($data_update);
43        }   
44
45        sleep(5); 
46
47        }       
48    	
49} catch (PDOException $ex) {
50    echo $ex->getMessage(); 
51}

保存文件,按CTRL +X,Y然后ENTER

一旦您完成了在 /var/www/html/tasks.php 文件中的逻辑编码,您将在下一步每 1 分钟执行该文件的 crontab daemon。

步骤 3 – 计划 PHP 脚本在 1 分钟后运行

在 Linux 中,你可以通过在 crontab 文件中输入命令来安排任务在规定的时间后自动运行。 在此步骤中,你将指示 crontab daemon 在每分钟后运行你的 `/var/www/html/tasks.php 脚本。

1sudo nano /etc/crontab

然后向文件的尽头添加以下内容,以便在每分钟执行http://localhost/tasks.php:

1[label /etc/crontab]
2...
3* * * * * root /usr/bin/wget -O - http://localhost/tasks.php

保存并关闭文件。

本指南假定您对 cron 工作如何工作的基本知识。 考虑阅读我们关于如何在 Ubuntu 上使用 Cron 来自动化任务的指南(https://andsky.com/tech/tutorials/how-to-use-cron-to-automate-tasks-ubuntu-1804#understanding-how-cron-works)。

如前所述,尽管 cron daemon 每 1 分钟运行 tasks.php 文件,一旦该文件首次执行,它将循环通过打开的任务再持续 60 秒。

在更新和关闭 /etc/crontab 文件后,crontab 戴蒙应该立即开始执行您在 tasks 表中插入的 MySQL 任务. 为了确认一切是否按预期工作,您将查询您的 cron_jobs 数据库。

步骤4:确认工作执行

在此步骤中,您将再次打开您的数据库,以检查tasks.php文件是否在自动执行时处理排队任务。

重新登录到您的 MySQL 服务器作为 root:

1sudo mysql -u root -p

然后,输入您的MySQL服务器的根密码,然后按ENTER,然后切换到数据库:

1USE cron_jobs;
1[secondary_label Output]
2Database changed

任务表运行SELECT语句:

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

您将收到类似于以下的输出。在完成_at列中,任务已在5秒间隔内处理,并且任务已被标记为完成,因为已处理列现在已设置为Y,意思是YES

 1[secondary_label Output]
 2
 3+---------+-----------+---------------------+---------------------+--------------+
 4| task_id | task_name | queued_at           | completed_at        | is_processed |
 5+---------+-----------+---------------------+---------------------+--------------+
 6|       1 | TASK 1    | 2021-03-06 06:27:19 | 2021-03-06 06:30:01 | Y            |
 7|       2 | TASK 2    | 2021-03-06 06:27:28 | 2021-03-06 06:30:06 | Y            |
 8|       3 | TASK 3    | 2021-03-06 06:27:36 | 2021-03-06 06:30:11 | Y            |
 9+---------+-----------+---------------------+---------------------+--------------+
103 rows in set (0.00 sec)

这证实你的PHP脚本按预期运行;你在更短的时间间隔内运行任务,因为你忽略了Crontab DAEMON设置的1分钟时间限制。

结论

在本指南中,你已经在 Ubuntu 20.04 服务器上设置了样本数据库,然后,你在表中创建了工作,并使用 PHP while(...){...} 循环和 sleep() 函数以 5 秒的间隔运行它们。

有关更多 PHP 教程,请参阅我们的 PHP 主题页面

Published At
Categories with 技术
comments powered by Disqus