creatcode / cronweave
定时任务调度引擎
Requires
- php: >=7.4
- ext-curl: *
- ext-json: *
- creatcode/crontab-expression: >=1.5.2
Requires (Dev)
- workerman/redis-queue: ^1.0
- workerman/workerman: ^4.0
Suggests
- ext-pcntl: Linux下多进程执行与超时控制需要
- workerman/redis-queue: 使用Workerman驱动时需要
- workerman/workerman: 使用Workerman驱动时需要
Provides
None
Conflicts
None
Replaces
None
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读取,引擎零感知。需要按字段查询(如"该设备有哪些任务")、字段是表单必填主体时用这种方式。 extraJSON(适合可选执行参数):不需要 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 检查验证正确性,不等同于生产数据库压力测试;尚未进行生产业务规模及长时间稳定性测试。全部测试使用独立目录和随机前缀队列并自行清理。