Search by

creatcode / cronweave

creatcode

定时任务调度引擎

Package info

github.com/creatcode/cronweave

Type:think-extend

pkg:composer/creatcode/cronweave

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-11-10 06:04 UTC

This package is auto-updated.

Last update: 2026-11-10 06:08:38 UTC


README

适用于 ThinkPHP 定时任务调度引擎。它使用 cronweave、cronweave_log、Redis 队列 cronweave 及 runtime/cronweave/, 支持常驻进程/Workerman双驱动,适配ThinkPHP各版本

安装

composer require creatcode/cronweave

运行 php think cronweave install 按宿主前缀建调度表(IF NOT EXISTS,已存在则跳过);

发布后的配置文件为 cronweave.php。主要配置如下:

return [
    'table' => 'cronweave',
    'log_table' => 'cronweave_log',
    // error: 仅失败任务;all: 成功和失败任务均写执行日志。
    'log_mode' => 'error',
    'runtime_directory' => '',
    'redis' => [
        'host' => '127.0.0.1',
        'port' => 6379,
        'password' => '',
        'db' => 0,
        // 留空时队列键就是 cronweave。
        'prefix' => '',
    ],
    // type => TaskHandlerInterface 实现类
    'handlers' => [],
];

log_mode 默认为 error,避免高频成功任务持续写入日志表;需要完整执行审计时设置为 all。

handlers 决定自定义任务类型如何执行;内置 url、sql、shell 类型不需要额外注册。任务的通用内容放在 content。

自定义类型的参数有两种存放方式,按需选择:

  • 专用字段(推荐用于核心业务字段):引擎读取任务时整行透传(SELECT *),宿主对任务表自行 ALTER TABLE 加列后,handler 直接从 $task 读取,引擎零感知。需要按字段查询(如"该设备有哪些任务")、字段是表单必填主体时用这种方式。
  • extra JSON(适合可选执行参数):不需要 SQL 查询、仅 handler 自己消费的松散参数(如重试次数、超时秒数)以 JSON 放在 extra 列,加新参数不用改表。
use Creatcode\Cronweave\Contract\TaskHandlerInterface;
use Creatcode\Cronweave\TaskResult;

class DeviceCommandHandler implements TaskHandlerInterface
{
    public function execute(array $task): TaskResult
    {
        // 专用字段: ALTER TABLE加列后直接可用
        $deviceIds = $task['device_ids'] ?? '';
        // 可选参数: extra JSON
        $extra = json_decode((string)($task['extra'] ?? ''), true) ?: [];
        // 按宿主业务发送设备指令。
        return TaskResult::success('已发送');
    }
}

// cronweave.php
'handlers' => ['device_command' => DeviceCommandHandler::class],

扩展类型的后台表单由宿主自行提供(内置插件只管理 url、sql、shell);插件保存任务时提交什么更新什么,未提交的字段(含 extra)保持原值不受影响。

运行

本地常驻驱动开箱即用;Workerman 驱动需要额外安装(composer.json 中已声明为 suggest):

composer require workerman/workerman workerman/redis-queue
php think cronweave                                  # 本地常驻调度器
php think cronweave runone 123                       # 手动同步执行一次任务
php think cronweave start -w --worker=scheduler -d   # Workerman: 调度进程
php think cronweave start -w --worker=executor -d    # Workerman: 执行进程
php think cronweave status -w                        # Workerman: 进程状态
php think cronweave stop -w                          # Workerman: 停止

生产环境应分别守护 scheduler 和 executor 两个 Workerman 角色,避免调度扫描阻塞任务执行。cronweave.lock 仅是单机文件锁;多机部署需要外部分布式锁。

Windows

Workerman 在 Windows 下不支持单进程内运行多个 worker 脚本,--worker=all 会被拒绝。包内提供了批处理脚本(自动定位项目根目录,同一窗口后台启动 scheduler 与 executor,关闭窗口即停止):

vendor\creatcode\cronweave\bin\cronweave-start.bat

也可手动分别执行 php think cronweave start -w --worker=scheduler 与 php think cronweave start -w --worker=executor。

Redis List/BRPOP 为至多一次语义:任务被消费者弹出后进程异常退出时,不会自动确认或重投。

执行约定与回归测试

本地 URL 任务的 content 作为单个路由参数传给当前 PHP 可执行文件,不解析 Shell 操作符或额外命令行参数。Shell 任务仍按命令字符串执行;两类任务均以退出码 0 为成功,无输出也可成功,非零退出码会记录失败及标准错误输出。宿主需允许使用 proc_open。

HTTPS 请求校验证书链和主机名,宿主需配置可用的 CA 证书;自签名证书需加入宿主信任链。客户端显式采用宿主的 curl.cainfo,未配置时使用 cURL 默认信任库。

任务的 content 可使用 URL 参数 GET|POST 格式,末尾方法可省略(默认 POST);参数中间的空格会保留。仅 HTTP 2xx 记为成功,未跟随的重定向记为失败,不自动跳转。SQL 支持引号、普通注释和多语句,查询只执行一次;不支持 DELIMITER 和 MySQL 条件注释。DDL 仍受 MySQL 隐式提交规则约束。

输出日志最多保留 60000 字节,超出时标记 [output truncated]。HTTP、进程输出持续读取并丢弃超额部分,SQL 结果日志也会截断;SQL 查询结果本身仍由宿主驱动读取,批量查询应自行限制结果集。

秒级调度与重叠策略

'executor_count' => 20,
'allow_overlap' => false,
'execution_timeout' => 90,
'queue_wait_timeout' => 5,

不同任务在执行器容量内并行。同一任务默认不重叠,上一轮执行中则跳过本轮并记录原因;单任务可用 extra JSON 中的 {"allow_overlap": true} 覆盖全局值。跳过沿用现有 failure 日志状态,内容说明原因,不增加执行次数。

任务在真正执行前重新读取,检查是否删除、停用、过期或修改了周期;抢占与次数增加也发生在执行前,投递失败、等待超时和重叠跳过不消耗次数。已经开始的尝试无论成功与否都计一次,仍遵守 maximums;maximums=0 时,单次业务失败不会停用后续周期。

每分钟预计算触发时刻,并在当前分钟末秒提前准备下一分钟。Workerman 以 20ms 间隔按绝对时间检查计划,同秒任务共用一个 Timer 和一次状态查询;ThinkPHP 适配器只批量读取本次到期的任务,自定义仓库未提供 fetchNormalTasksByIds() 时回退到现有 fetchNormalTasks() 接口。执行器仍保留最终状态核对、互斥和原子次数更新。

启动前已经过去的时刻不补跑;启动后的短暂延迟允许在 queue_wait_timeout 内执行,超过期限则跳过,等待判断保留小数秒精度。数据库暂断后会重试,恢复后继续调度。分钟边界先投递已准备的零秒任务,再补刷新并按任务和计划秒去重,避免紧邻边界新增或改周期漏掉首轮。停用、删除及任务内容仍在执行前重新核对,全局配置修改需要重启调度器和执行器。queue_wait_timeout 是等待容忍度,不是准时程度设置。

Linux 本地并行需要 pcntl、posix、stream_socket_pair,允许 pcntl_fork、pcntl_waitpid、pcntl_signal、pcntl_alarm、posix_setsid、posix_kill。每个工作进程执行一条任务后补建,异常或超时隔离在该进程中。执行器使用独立进程组,超时和停止时清理同组 PHP/Shell 子进程;任务应以前台方式运行,不得自行脱离进程组。

Windows 本地驱动保持串行兼容,不提供 Linux 的 fork 隔离和强制超时能力。需要 Windows 并行时,可以分别启动多个 Workerman executor 进程,它们共享 Redis 和任务互斥锁。生产服务器仍需配置进程守护。秒级调度受数据库、系统负载和执行器容量影响,不属于硬实时保证;队列弹出后进程崩溃仍可能丢失本轮任务,不提供执行副作用的恰好一次保证。

升级时先停止调度、等待已有队列处理完毕,再同时更新调度器和执行器。不要混用新旧版本的执行进程。

回归命令

  • composer test:业务执行器回归、调度故障注入和 Linux 进程回归;无 Linux 能力时,最后一项明确输出 SKIP。
  • php tests/linux.php:Linux 真实 fork、并行、同任务重叠、失败后继续触发、直接退出后补建执行器、超时终止真实子进程、SIGTERM 清理及父进程被强杀后的锁释放。使用临时文件夹具,不访问业务库。
  • php tests/linux.php --latency:本地 fork 延迟压测,覆盖轻负载、同秒突发、快慢任务混合、执行器饱和和跨分钟;输出触发数、P50/P95/P99 和最大启动延迟。
  • php tests/mysql.php <现有ThinkPHP项目目录> <本地.env路径>:借用宿主原生 ORM 和数据库连接,仅创建会话临时表,检查次数、查询、事务回滚及日志持久化;账户需有创建临时表权限。
  • CRONWEAVE_TEST_VENDOR=/path/to/vendor php tests/redis.php 16379:连接本机测试 Redis,使用真实 Workerman 调度器和多个执行器;Linux 下还验证超时、子进程清理和直接退出后的恢复。依赖 workerman/workerman、workerman/redis-queue。测试只操作随机前缀的队列键并自行清理。
  • CRONWEAVE_TEST_VENDOR=/path/to/vendor php tests/redis.php 16379 --latency:同样的延迟场景,使用真实 Workerman 进程池及 Redis;测试 Redis 应独立于业务实例。
  • python -B tests/http_integration.py /path/to/php:本机 HTTP/HTTPS 实测,包含可信及不可信证书、重定向、参数及大输出;仅此集成脚本使用 Python 的已安装 cryptography,不增加生产依赖。

各集成测试创建的进程、随机前缀队列、临时表、证书和文件在结束时清理。模拟时钟或替身测试通过,不代表 Linux 真实进程验收通过。

Linux 实机回归记录(2026-11-10)

在 Linux 6.8 x86_64、PHP 7.4.34、Workerman 4.1.8、Redis 8.8.4 上通过本地 fork 和真实 Redis 多执行器回归,覆盖重叠策略、失败后继续触发、进程直接退出后的补建、强制超时、真实 PHP 子进程清理、停止清理及父进程被强杀后的锁释放。失败、超时和异常退出的执行次数均保留。精度数据采用下表独立采样,不把进程启动前已经过去的时刻当作启动延迟样本。

原生 HTTP/HTTPS 回归通过,可信 CA 请求成功,不可信 CA 仍被拒绝;同时通过基础执行器、58 项调度替身检查、表达式和 PHP 语法检查。TP5/TP6/TP8 原生 ORM 与 MySQL 临时表回归各通过 43 项检查。宿主默认 CLI 禁用了 pcntl_waitpid,测试仅使用隔离的附加 INI 启用所需函数并限定临时目录,未修改全局 PHP 配置。

200ms 启动延迟验收(2026-11-10)

测试机为 2 vCPU、约 5.7GiB 内存。下表使用真实 fork 或 Workerman/Redis;任务仓库是临时文件夹具,每次外层查询、抢占和读回状态注入 10ms 延迟。指标从计划秒计到自定义 PHP 处理器入口,不包含业务完成时间或远端 HTTP/设备响应。每个场景采样六个计划秒,提前两秒启动执行器;跨分钟场景覆盖 58、59、00、01、02、03 秒。

场景 每秒任务/执行器 每个驱动启动数 fork P99 / 最大(ms) Redis P99 / 最大(ms)
空操作 5 / 20 30 / 30 65.61 / 65.61 82.06 / 82.06
同秒突发,每次20ms 20 / 20 120 / 120 108.73 / 111.61 100.68 / 101.99
5个500ms慢任务+15个20ms任务 20 / 20 120 / 120 113.79 / 113.91 94.19 / 94.65
跨分钟,每次20ms 20 / 20 120 / 120 103.15 / 106.77 127.59 / 137.96
超载,每次400ms 40 / 4 52 / 240 1023.60 / 1023.60 1019.31 / 1019.31

前四个场景共 780 次启动,全部满足 200ms 目标,无缺失、无重复。超载场景记录积压及跳过,明确不算 200ms 达标。执行器容量应结合同时到期数量和任务占用时间配置。

这些结果是上述机器和短时负载下的观测,不是所有宿主的固定延迟承诺。原生 MySQL 检查验证正确性,不等同于生产数据库压力测试;尚未进行生产业务规模及长时间稳定性测试。全部测试使用独立目录和随机前缀队列并自行清理。