队列消费必须封装为独立任务类并实现fire()(tp6)或handle()(tp8)方法,禁止在控制器中直接操作db;否则无法重试、不可监控、并发易错。

队列消费必须写成独立任务类,不能在控制器里直接调用 DB 操作;否则无法重试、不可监控、并发下易出错。
任务类必须实现 fire() 方法(TP6+)或 handle() 方法(TP8)
ThinkPHP 6 和 8 对任务类接口不兼容:TP6 要求实现 fire(Job $job, $data),TP8 改为 handle(Job $job, $data)。混淆会导致任务静默失败,消费者进程无报错但不执行。
- TP6 示例:
namespace appjob; use thinkqueueJob; class ClearUserCacheJob { protected $uid; public function __construct($uid) { $this->uid = $uid; } public function fire(Job $job, $data) { Cache::delete('user_' . $this->uid); $job->delete(); // 成功后删任务 } } - TP8 必须用
handle(),且类需继承think\queue\Job或使用 trait;直接复用 TP6 类会跳过执行 - 别在
fire()/handle()里 throw 异常来“控制重试”——框架只在未调用$job->delete()时自动重试;异常未捕获会导致进程崩溃
php think queue:work 是唯一生产可用的消费命令
php think queue:listen 在 TP6+ 中已被移除或不注册,强行运行会报 Command "queue:listen" is not defined。即使旧版能跑,它也是轮询模式,每次拉取都 reload 全框架,内存持续增长,1 小时后可能 OOM。
- 正确启动方式:
php think queue:work --queue=default --sleep=3 --max-jobs=500 -
--sleep=3表示空闲时休眠 3 秒,避免 Redis 频繁连接;--max-jobs=500是防内存泄漏的硬限制,到数自动退出并由 Supervisor 拉起新进程 - 切勿加
--daemon:TP6.3+ 已废弃该参数,加了会报错或降级为普通模式,失去信号控制能力 - 如果执行后立即退出,先检查
config/queue.php中'driver'是否拼错(如写成'redis'而非'redis'—— 实际应为小写,但配置键名是'driver',不是'type')
数据库操作必须封装进事务 + 显式错误处理
队列任务里执行 Db::transaction() 不等于安全:若任务中途被 kill(如 Supervisor 触发内存超限重启),事务不会自动回滚,DB 状态和队列状态就脱节了。
- 所有 DB 写操作前,先查一遍业务状态(例如:更新用户积分前,
SELECT balance FROM user WHERE id = ? FOR UPDATE),避免重复执行 - 更新成功后,再调用
$job->delete();失败则不删,靠框架重试(前提是没抛未捕获异常) - 禁止在任务中调用
exit、die或dd(),它们会终止 worker 进程,导致后续任务全部积压 - 如果 DB 操作耗时超过
retry_after(默认 90 秒),Redis 会把任务重新入队,造成重复执行——务必设大该值,如'retry_after' => 300
Supervisor 配置漏掉 environment 就会连不上 Redis
本地 php think queue:work 能跑,丢到服务器上就卡住不动?大概率是 Supervisor 启动时没加载 .env,导致 REDIS_HOST 为空,连接超时后静默等待,不报错也不退出。
- Supervisor 的
environment必须显式声明:environment=PATH="/usr/bin",HOME="/root",APP_ENV="production",REDIS_HOST="127.0.0.1"
- 不要依赖
php think queue:work自动读.env—— 它在 Supervisor 下运行时工作目录可能是/,根本找不到.env - 日志路径要用绝对路径:
stdout_logfile=/var/log/think-queue-worker.log,否则日志写入失败,问题无法排查 - 确认
user配置项权限足够,尤其当 Redis 密码含特殊字符(如@、/)时,URL 编码要提前做好
真正难的不是写消费逻辑,而是让每个任务在失败、重启、并发、超时各种边界下,都能保持 DB 和缓存状态一致——这要求你把重试当成常态,而不是异常。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











