think-queue默认使用sync驱动,任务同步执行而非异步;必须手动修改config/queue.php的default为redis等异步驱动,启动消费者进程(如php think queue:work),并正确实现fire()方法及失败处理逻辑,否则任务不会真正入队执行。

直接说结论:think-queue 不是开箱即用的“全自动队列”,它默认走 sync 驱动——也就是同步执行,根本没异步。你必须手动改配置、启消费者、处理失败逻辑,否则任务永远卡在内存里不跑。
为什么 push 了任务但什么都没发生?
这是最常踩的坑:调用 think\facade\Queue::push() 后页面返回成功,但日志没输出、数据库没写入、邮件没发——因为 default 配置仍是 'sync',任务当场就执行了(且无日志、无异常捕获),你以为它进队列了,其实只是换个方式同步跑。
- 检查
config/queue.php中的'default' => 'sync',这不是开发模式开关,是真实驱动名 -
sync驱动下,$job->delete()和$job->release()全部无效,因为没持久化过程 - 想验证是否真异步,把任务里加个
sleep(5),再看 HTTP 响应时间:如果还是秒回,说明还在 sync;如果卡住 5 秒,才是真同步执行(也说明你还没切到异步驱动)
Redis 驱动配置最容易漏掉的三项
Redis 是最常用也最易配错的驱动。光写 'driver' => 'redis' 不够,下面三个键必须显式声明,否则会静默 fallback 到 sync 或报连接超时:
-
'host'和'port':不能依赖predis的默认值,ThinkPHP 8.0+ 的think-queuev3.x 不读取predis全局配置,必须在connections.redis下重写 -
'database':尤其当 Redis 有多个 DB 时,'select'参数已废弃,必须用'database'指定数字(如0或4) -
'retry_after':这个值必须大于任务最长执行时间,否则 Redis 里的任务会被当成“卡死”自动重发——比如你发短信要 8 秒,这里设成5,就会重复发两次
正确片段示例:
Think-Queue是ThinkPHP官方推出的高性能消息队列扩展,完美适配 ThinkPHP 8.1.4 及 PHP 8.5。它支持 Redis、Database、Beanstalkd 等多种驱动,提供任务延迟执行、失败自动重试、并发守护进程及事件监听机制。
立即学习“PHP免费学习笔记(深入)”;
return [
'default' => 'redis',
'connections' => [
'redis' => [
'driver' => 'redis',
'queue' => 'default',
'connection' => 'default',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
'retry_after'=> 120, // 关键:至少比最长任务多 30 秒
],
],
];
任务类里 fire() 和 handle() 能混用吗?
不能。think-queue v3.x(TP6/TP8)只认 fire() 方法;v2.x(TP5.1)才支持 handle()。如果你用的是 TP6+ 却写了 handle(),消费者进程会静默跳过该任务,连错误日志都不打。
- 任务类方法签名必须是
public function fire(\think\queue\Job $job, $data),少一个参数或类型不对,就直接抛ArgumentCountError -
$job->attempts()返回的是当前重试次数(从 1 开始),不是已执行次数,首次执行时返回1 - 别在
fire()里 try/catch 全包然后吞掉异常:一旦出错且没调$job->delete(),任务会一直留在队列里,按retry_after间隔反复重试,直到达到最大次数触发failed()
消费者进程必须手动启动,且不能靠 Web 请求触发
没有“自动监听”这回事。Web 请求中调用 Queue::push() 只是写入 Redis/DB;真正取任务、执行、删任务,全靠你手动运行的常驻进程:
- 命令是
php think queue:listen(实时监听)或php think queue:work(单次执行后退出),不是php think queue:run(旧版命令,v3.x 已移除) - 生产环境必须用
supervisor或systemd守护,否则终端一关进程就死;本地开发可加--verbose --delay=3看详细日志 - 不要在控制器里写
exec('php think queue:work &'):PHP 的exec启的子进程会随 Web 请求结束被 kill,根本跑不起来
复杂点在于:同一个 Redis 实例下,不同环境(dev/staging/prod)必须用不同 queue 名隔离,否则测试环境的失败任务可能被生产环境的消费者捞走执行——这个细节 90% 的文档都漏掉了。


















