ThinkPHP 6.1+ 废弃 timer:run 长轮询模式,改用 cron 调用 timer:check 单次校验,要求任务幂等、isDue() 快速无副作用、execute() 自行加分布式锁,并严格遵循 PSR-4 命名与日志规范。

ThinkPHP 6.1+ 的定时任务不再支持原生的 php think timer:run 自动轮询模式,必须改用独立进程 + 外部调度器(如 Linux cron)驱动 php think timer:check。
为什么 timer:run 在升级后失效了
TP6.1 起移除了内置长运行守护进程逻辑,timer:run 命令退化为单次扫描执行,无法持续监听。直接在后台用 nohup php think timer:run & 启动会立即退出或漏触发——它本质已不是守护命令。
- 旧版(TP5.x / TP6.0)依赖
pcntl_fork和信号处理,但 PHP-FPM 环境下不可用,线上常靠 supervisor 强行维持,稳定性差 - 新版统一收口到
timer:check:只做「本次该不该执行」判断,不阻塞、不循环,适合被 cron 每分钟调用一次 - 迁移后所有任务必须满足「幂等性」:同一任务可能因 cron 重叠或网络延迟被重复触发
Linux cron 配置 timer:check 的最小可靠写法
不要用 * * * * * cd /path/to/app && php think timer:check 这类裸写法——路径错误、环境变量缺失、PHP 版本不一致都会导致静默失败。
- 用绝对路径:确保
php是你部署环境实际使用的二进制(如/usr/bin/php8.1),避免系统默认老版本解析失败 - 显式指定工作目录和环境:
* * * * * cd /data/www/myapp && /usr/bin/php8.1 think timer:check >> /data/log/timer.log 2>&1 - 加锁防重入(关键):在命令前加
flock -n /tmp/timer.lock -c '...',避免上一周期未结束时新 cron 进程并发执行 - 日志必须分开:
timer:check输出极少,但错误堆栈全在 stderr,2>&1不可省
任务类中 isDue() 和 execute() 的协作要点
新版调度完全依赖任务类的 isDue() 返回布尔值决定是否调用 execute()。这个判断不能依赖外部状态缓存(如 Redis 键是否存在),而应基于确定性时间表达式。
立即学习“PHP免费学习笔记(深入)”;
-
isDue()必须快:建议只做date('i') === '30'或cron_match('* * * * *', $now)类计算,禁止查数据库、HTTP 请求 - 避免「上次执行时间」判据:因为
timer:check是无状态调用,没有上下文记录上一次啥时候跑过 -
execute()内务必自己加分布式锁(如Cache::store('redis')->lock('task:sync_user', 300)),否则多台服务器同时拉 cron 会重复干活 - TP 自带的
$this->once()只对单机有效,跨机器无效
从 ThinkPHP 5.x 迁移时最易忽略的三处
不是改个命令就能跑通。很多项目卡在这些细节上:
-
app/timer.php中定义的任务类命名空间必须与文件路径严格一致(TP6 要求 PSR-4),旧版允许别名映射,新版直接报Class not found - TP5 的
Task::execute()可以返回字符串用于日志,TP6+ 的execute()返回值被忽略,日志得自己写Log::info() - 如果用了自定义的
TimerService替换默认调度器,升级后必须重写dispatch()方法,使其兼容timer:check的单次调用模型,而非轮询模型
真正麻烦的从来不是「怎么配 cron」,而是任务逻辑里那些隐含的「我假设它只会被调一次」的判断——现在得主动防御重复执行。



















