作者选择了 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 秒的间隔执行您的表中的任务。
前提条件
要完成本教程,您需要以下内容:
- 一个 Ubuntu 20.04 服务器与非 root 用户建立. 遵循我们的 Initial Server Setup with Ubuntu 20.04]指南.
- 一个 LAMP 堆栈设置在您的服务器上。 请参阅 How To Install Linux, Apache, MySQL, PHP (LAMP) stack on Ubuntu 20.04 指南。 对于本教程,您可以跳过 ** Step 4 — Creating a Virtual Host for your Website**.
步骤 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_PREPARES
到false
,让原生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 主题页面。