ThinkPHP 8 的定时任务与队列是协作关系:定时任务决定“何时触发”,队列保障“如何可靠执行”;TP8 无内置调度器,须用 crontab 调用 think 命令触发,队列推荐 think-queue + Redis 实现异步、重试与延迟。

ThinkPHP 8 的定时任务和队列不是“二选一”,而是各司其职的协作关系:定时任务决定“什么时候触发”,队列负责“怎么可靠执行”。搞不清这个分工,容易把逻辑塞错地方,导致任务丢失、重复或静默失败。
定时任务只做触发,不承担执行
TP8 自身不提供常驻内存的调度器,必须依赖系统级 crontab 触发 think 命令。你写的命令类(如 app\command\SyncOrderCommand)只封装业务逻辑,不负责轮询、重试或并发控制。
- crontab 条目要写绝对路径:例如
* * * * * /www/server/php/74/bin/php /var/www/myapp/think sync:order >> /tmp/cron-sync.log 2>&1 - 必须显式切换工作目录,否则自动加载和配置会失效;日志重定向不可省略,否则失败无迹可寻
-
think schedule:run不是守护进程——它只检查当前时间点该执行哪些任务,且默认仅在APP_ENV=production下生效;开发调试需加APP_ENV=local php think schedule:run -v
队列专管执行可靠性
一旦定时命令触发,真正耗时、易失败、需重试或延迟的操作(如发邮件、调第三方 API、取消超时订单),应交给队列异步处理。TP8 生产环境推荐 think-queue + Redis 组合。
- 任务类必须定义
fire(Job $job, $data)方法(不是handle或execute),签名错误会导致静默忽略 - 发布时传字符串类名:
Queue::push('app\job\SendEmailJob', $data, 'email');监听命令也得匹配队列名:php think queue:listen --queue email - 成功后务必手动调用
$job->delete();失败可用$job->release(120)延迟重试,避免无限循环消费
延迟与超时场景靠 Redis zset 实现
订单超时取消、消息延迟投递这类需求,别用数据库轮询或 sleep 模拟。Redis 有序集合(zset)天然支持按时间戳排序,think-queue 的延迟队列底层正是基于此,精度高、资源省。
立即学习“PHP免费学习笔记(深入)”;
- 发布延迟任务:
Queue::later(300, 'app\job\CancelOrderJob', $data),表示 5 分钟后执行 - 对应任务类中仍用
fire()处理,无需额外判断时间;Redis 自动在指定时刻将任务推入待消费队列 - 避免在 fire() 中执行阻塞操作(如未设 timeout 的 curl 请求),否则会卡住整个队列进程
环境与生命周期必须隔离
定时命令和队列消费者运行在不同上下文:前者是短命的一次性进程,后者是长连接的常驻进程。它们共用配置但不能共享状态。
- 队列消费者建议用 supervisor 或 systemd 管理,设置自动重启;而 crontab 只管按时拉起命令,不干预后续
- 日志分开归档:定时任务日志写入
runtime/log/cron/,队列日志单独存到runtime/log/queue/,便于排查是触发环节还是执行环节出问题 - 数据库连接、Redis 客户端等资源,在每个 job 的
fire()开始时初始化,结束时释放,不依赖全局单例



















