simbiat/cron

使用数据库存储任务的Cron。


README

什么是

尽管名称是CRON,但这仍然是一个任务调度器,它使用MySQL/MariaDB数据库来存储任务及其调度。

为什么

最初,我的fftracker托管在用户无法访问CRON的服务器上,因此我将实体更新的任务(以及更多)存储在数据库中,并通过服务器端事件(SSE)触发它们。当Tracker迁移到更好的服务器时,这种方法保持了少量变化,并允许在没有适当的PHP库进行适当并行处理(或多线程)的情况下进行并行处理。

特性

  1. 既可用于CLI,也可从网页调用。
  2. 如果从网页调用,将按SSE规范输出头信息和状态。
  3. 如果从网页调用并且设置了SSE循环,将循环执行,直到网页关闭。
  4. 设置存储在数据库中。
  5. 任务具有类型,存储在数据库中,允许您基于相同的任务复制多个实例,但具有不同的参数和设置。
  6. 任务类型可以是对象,而不仅仅是函数。
  7. 任务类型支持附加方法,可以在实际函数执行之前调用,每个方法都有其附加的可选参数。
  8. 支持任务实例的一次性执行。
  9. 支持1秒精度的频率(可以每秒运行一次)。
  10. 支持基于星期几或月份几号的限制。
  11. 记录作业的执行,包括错误。
  12. 自动重置挂起的作业和自动清除旧日志。
  13. 允许全局禁用任务处理,但仍允许其管理。

如何使用

安装

  1. 下载(手动或通过composer)。
  2. 使用我的Database类建立数据库连接。
  3. 安装
(new \Simbiat\Cron\Agent())->install();

由于当前的设计,在以这种方式创建表之后,如果您将使用相同的脚本,您需要重新创建对象。

触发处理

要触发处理,只需运行以下命令

(new \Simbiat\Cron\Agent())->process(10);

其中10是您想要运行的最大任务数。预计您将将其放在某个.php文件中,该文件将由某些系统任务调度器(如*NIX系统中的实际Cron)触发。

此命令将执行以下操作

  1. 将脚本执行时间限制设置为0。
  2. 如果从CLI之外启动,忽略用户中断,并发送适当的HTTP头信息以用于SSE模式。
  3. 调用函数以重新调度所有挂起的作业(可以手动使用(new \Simbiat\Cron\Agent())->unHang()调用)。
  4. 调用函数以清除旧日志(可以手动使用(new \Simbiat\Cron\Agent())->logPurge()调用)。
  5. 更新数据库以随机 "id",该 "id" 将代表当前进程并有助于防止多个进程同时运行时的任务重叠或空运行。更新将仅针对在给定秒内应执行的选择任务数进行。
  6. 触发每个任务(可选循环)。

任务运行默认应返回 boolean 值,但此功能可以扩展(见下文)。除非值被扩展,否则仅 true 被视为实际成功。任何其他值都将被视为 false。此值将转换为字符串并记录,因此建议在调用函数内部进行自己的错误处理,特别是考虑到,按设计,此库 无法保证 捕获这些错误。

任务管理

添加任务

要使用此库,您需要使用以下命令至少添加1个任务。

(new Cron\Task())->settingsFromArray($settings)->add();

$settings 是一个关联数组,其中每个键都是以下设置之一

  1. task 是任务的强制性名称,它将被视为一个 UNIQUE ID。限制为 VARCHAR(100)
  2. function 是将被调用的强制性函数名称。限制为 VARCHAR(255)
  3. object 可以使用,如果您的 $function 只能从对象中调用。您必须指定对象的全名以及所有命名空间,例如 \Simbiat\FFTracker。限制为 VARCHAR(255)
  4. parameters 是可选参数,在创建可选的 $object 时将使用这些参数。需要是纯数组或值的 JSON 编码数组,可以扩展(使用 ... 操作符)。作为 JSON 字符串存储在数据库中,限制为 VARCHAR(5000)
  5. allowedreturns 是可选的返回值,将被视为 "成功"。默认情况下,该库依赖于 boolean 值来确定任务是否成功完成,但此选项允许扩展可能的值。可以是纯数组或值的 JSON 编码数组,可以扩展(使用 ... 操作符)。作为 JSON 字符串存储在数据库中,限制为 VARCHAR(5000)
  6. description 是任务的任意描述。限制为 VARCHAR(1000)
  7. system 表示任务是否可以被此类删除。
  8. maxTime 允许函数运行的最大时间(将在运行任务之前更新执行时间限制)。默认为 3600

使用 $task 调用此函数,其中已注册,将更新相应的值,但除 system 之外。

parameters 参数还支持特殊的数组键 'extramethods'。这是一个类似这样的多维数组

'extramethods' =>
    [
        [
            'method' => 'method1',
            'arguments' => [argument1, argument2],
        ],
        [
            'method' => 'method2',
            'arguments' => [argument3, argument4],
        ],
    ]

每个子数组中的每个 method 都是需要在 $object 上调用的方法的名称,而 arguments 是传递给该 method 的可扩展的参数数组。这些方法将按数组中注册的顺序执行。

请注意,每个方法都应该返回一个对象(通常是 return $this),否则链可能会失败。

在创建对象时也可以通过将任务名称传递给 Task 构造函数从数据库加载设置

(new \Simbiat\Cron\Task('taskName'));

删除任务

要删除任务,将适当的 taskName 传递给构造函数并调用 delete

(new \Simbiat\Cron\Task('taskName'))->delete();

注意,设置 system 标志为 1 的任务将不会被删除。

将任务设置为系统任务

如果您是从头创建任务,则只需在设置数组中将 system 设置为 1。如果是现有任务,请这样做

(new \Simbiat\Cron\Task('taskName'))->setSystem();

此标志不能从类中设置为 0,因为这会违反其安全目的。要删除它 - 直接更新数据库。

调度

实际的调度是通过 "任务实例" 来完成的,这些实例由 TaskInstance 类管理。

添加任务实例

要安排任务,请使用此函数

(new Cron\TaskInstance())->settingsFromArray($settings)->add();

$task 是任务的强制性名称。

$arguments 是可选参数,将被传递给函数。需要是纯数组或值的 JSON 编码数组,可以使用扩展运算符(... 操作符)。存储在数据库中为 JSON 或空字符串,且限制为 VARCHAR(255)(由于 MySQL 限制)。同时支持特殊字符串 "$cronInstance"(如果是 JSON 编码的),在运行时将被任务实例值替换,这在你需要多个实例且需要偏移它们的处理逻辑时很有用。

$instance 是可选的实例编号(或你喜欢的 ID)。默认情况下,它是 1。当你想为相同参数的相同任务创建多个实例,且希望并行运行时,这很有用。

$frequency 控制任务应该运行的频率(以秒为单位)。如果设置为 0,则表示任务实例是一次性的,因此它将在成功运行后从计划中移除(而不是从任务列表中移除)。

$message 是在 SSE 模式下运行时显示的可选自定义消息。

$dayofmonth 是可选的整数数组,它限制了任务可以运行的月份中的天数。建议仅在将 $frequency 设置为 86400(1 天)时使用它,否则可能会引起意外的偏移和延迟。需要是纯数组或值的 JSON 编码数组,可以使用扩展运算符(... 操作符)。存储在数据库中为 JSON 字符串,且限制为 VARCHAR(255)

$dayofweek 是可选的整数数组,它限制了任务可以运行的一周中的天数。建议仅在将 $frequency 设置为 86400(1 天)时使用它,否则可能会引起意外的偏移和延迟。需要是纯数组或值的 JSON 编码数组,可以使用扩展运算符(... 操作符)。存储在数据库中为 JSON 字符串,且限制为 VARCHAR(60)

system 表示任务是否可以被此类删除。

nextrun 表示安排任务实例下次运行的计划时间。如果在创建任务实例时未提供,将安排为当前时间,这将允许你立即运行它。

Task 类一样,在创建对象时也可以从数据库加载设置。

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1));

只有 'taskName' 是必需的,但如果你有多个任务实例,请确保传递相应的参数和实例,因为只有这三个的组合才能确保唯一性。

从调度中移除任务

要从计划中删除任务,请将适当的 $task$arguments 传递到

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1))->delete();

将任务实例设置为系统任务

如果你是从头创建任务实例,那么只需在设置数组中将 system 设置为 1。如果是现有任务,则这样做

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1))->setSystem();

手动触发任务实例

在某些情况下,你可能想手动触发一个任务。你可以这样做

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1))->run();

请注意,如果 run() 执行时在数据库中找不到任务,你将获得一个异常,这与自动处理不同,在这种情况下,函数会简单地返回 false,假设这是一个一次性实例,由另一个进程执行(尽管不太可能发生)。

手动重新调度任务实例

你也可以使用以下方式手动重新安排任务

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1))->reSchedule($result, $timestamp);

$result 是一个 boolean 值,指示最后一个任务运行(即使它没有发生)是否应被视为成功(true)或失败(false)。它决定了数据库中将更新哪个时间戳,以及是否删除一次性实例。

$timestamp 是可选的时间,你想设置。如果没有提供(null),将计算下一次运行的最佳时间。

下一次运行时间

你可以执行

(new \Simbiat\Cron\TaskInstance('taskName', 'arguments', 1))->updateNextRun();

为了获取下一次任务执行的推荐UNIX时间戳。它将根据数据库中当前 nextrun 值与当前时间之间的时间差以及实例频率,计算出可能遗漏了多少个任务。这是为了保持调度的一致性,所以如果你每天在 02:34 调度一个任务,它将始终在 02:34 执行(或者至少会尝试)。如果实例有 dayofweekdayofmonth,函数将找到从基于实例频率确定的日期开始,最早满足这两个限制条件的日期。

设置

要更改任何设置,请使用

(new \Simbiat\Cron\Agent())->setSetting($setting, $value);

$setting 是要更改的设置名称(《字符串》)。

$value 是设置的新的值(《整数》)。

所有设置在对象创建和触发自动处理时从数据库中获取。支持的设置如下

  1. enabled 控制处理是否可用。不会阻塞任务管理。布尔值,因此根据MySQL/MariaDB的设计,只接受 01。默认值是 1
  2. errorLife 是存储错误日志的天数。默认值是 30
  3. retry 是延迟执行失败的单次作业的时间(秒)。这些作业的频率设置为 0,因此如果失败可能会导致它们垃圾邮件。这个设置可以帮助避免这种情况。默认值是 3600
  4. sseLoop 控制在SSE模式下是否可以循环处理。如果在运行 process 周期后将其设置为 0,SSE 将发送 CronEnd 事件。如果设置为 1,它将继续循环处理,直到流关闭。默认值是 0
  5. sseRetry 是SSE连接重试的毫秒数。它还将用来确定如果没有任何线程或作业,循环应该睡眠多长时间,但将被视为秒数除以20。默认值是 10000(或大约为空周期8分钟)。
  6. maxThreads 是允许同时运行的最大线程数(或者更准确地说,是循环数)。不影响单个 runTask() 调用,只影响 process()。当前线程的数量由 schedule 表中 runby 的不同值确定,因此要注意错误处理(或没有错误处理),否则它很容易被挂起的作业轻易超过。默认值是 4

事件类型

以下是用于记录和输出SSE流的事件类型列表

  1. CronStart - 处理开始。
  2. CronFail - 处理失败。
  3. CronTaskSkip - 跳过了任务。
  4. CronTaskStart - 开始任务。
  5. CronTaskEnd - 任务成功完成。
  6. CronTaskFail - 任务失败。
  7. CronEmpty - 周期中任务列表为空。
  8. CronNoThreads - 在此周期上没有空闲线程。
  9. CronEnd - 处理结束。