tp6定时任务必须用系统crontab触发think命令,不可依赖http请求或php循环;命令类需手动管理数据库连接,高阶场景应结合redis延迟队列解耦调度与执行。

TP6 接口定时任务调度不能靠“接口被调用”来触发,必须脱离 HTTP 生命周期,走独立、可控、可监控的后台执行路径。核心是分清“调度触发”和“任务执行”两个环节:前者由系统级 crontab 控制节奏,后者由命令行入口 + 队列/延迟机制保障可靠性。
调度层:必须用系统 crontab 触发 think 命令
TP6 没有内置守护进程或调度器,任何 PHP while 循环、Web 请求触发、Swoole 定时器挂起等方式都不可靠——进程崩溃无感知、无法跨机协调、日志难追踪。
- crontab 规则必须写绝对路径,例如:
0 2 * * * /www/server/php/82/bin/php /www/wwwroot/myapp/think sync:order --limit=500 >> /www/wwwroot/myapp/runtime/log/cron-order.log 2>&1 - 务必显式切换工作目录(或确保 think 在项目根目录运行),否则自动加载失败、配置读取异常
- 日志重定向不可省略,推荐用专用 channel 记录,如 Log::channel('cron')->info('order sync started')
- 测试阶段先用 * * * * * 验证链路通不通,确认日志生成、数据库连接、业务逻辑无 fatal error 后再调整真实周期
执行层:命令类中规避容器陷阱,手动管理连接
在 appcommand 下编写的命令类里,不能依赖 $this->app->db 等自动注入对象,因为 CLI 环境下容器初始化不完整或存在连接复用问题。
- 数据库操作前强制初始化主库连接:app('db')->connect();若用读写分离或多库,对每个连接都调用一次 connect()
- 推荐改用 Db::connect('mysql') 或通过 execute 方法参数注入,例如:
public function execute(Input $input, Output $output, Connection $db) - 避免在 fire() 或 execute() 中直接 new Db 类或硬编码 config,保持可测、可替换
进阶协同:定时任务 + 队列 + Redis 延迟队列组合
单纯 cron 每分钟跑一次不够灵活,高阶场景需把“调度触发”和“精准执行”解耦:cron 负责轻量发布,Redis zset 负责毫秒级延迟与去重。
- cron 命令只做快速入队,例如:Queue::push('app\job\SendNoticeJob', $payload, 'notice')
- 延迟任务不用 think-queue 的 delay()(本质是轮询),而是手写 zadd:
redis()->zAdd('delay:notice', time() + 600, 'notice_'. $id) - 监听进程每秒 zRangeByScore 获取待执行项,处理完立即 zRem,配合 setNx 分布式锁防重复
- 任务类严格实现 fire(Job $job, $data),结尾必须调用 $job->delete(),异常时用 $job->release(60) 重试
替代方案说明:Workerman/Swoole 不是调度器,而是执行载体
Workerman 或 Swoole 可用于构建常驻进程执行高频任务(如每秒轮询设备状态),但它本身不是调度系统——它不提供 cron 表达式解析、任务启停管理、失败告警等能力。
- 若用 Workerman,需自行封装启动脚本(如 php think timer:start)、信号监听、日志隔离
- Swoole 需启用协程并禁用异步 Redis 客户端,否则在 task 进程中易出现连接混乱
- 这类方案适合内部服务通信、IoT 数据采集等低延迟强实时场景,但运维复杂度显著高于 crontab + 队列组合











