生产环境必须用queue:work而非queue:listen,因后者是开发轮询模式,内存累积、不重载代码、开销大;前者单进程常驻,支持优雅重启、信号控制,并需配合--sleep、--max-jobs等参数及supervisor正确配置。

为什么 php think queue:listen 不能直接丢进 systemd 或 supervisor 就完事
因为 queue:listen 是开发模式下的轮询监听,每次有任务就 fork 新进程执行,长期运行会累积内存、不自动重载代码、无法感知框架配置变更。生产环境必须用 queue:work,它单进程常驻,支持优雅重启和信号控制。
-
queue:listen每次执行都重新加载整个框架,开销大;queue:work启动一次后复用实例,更轻量 - 监听模式下,修改了任务类代码,必须手动 kill 进程再启;
queue:work --daemon已被废弃,新版 ThinkPHP 6+ 只支持非 daemon 模式 + 进程管理器兜底 - 没加
--sleep和--max-jobs容易卡死或无限占用 CPU,尤其在 Redis 连接异常时会疯狂重试
supervisor 配置里 command 必须带完整路径和明确参数
很多人写成 php think queue:work,结果 supervisor 找不到 think 文件,或工作目录不对导致配置加载失败。实际要锁定 PHP 解释器路径、项目根目录、队列连接名和超时策略。
- 用绝对路径:比如
/usr/bin/php /var/www/myapp/think queue:work redis --sleep=3 --max-jobs=1000 --tries=3 -
--queue参数要和你投递任务时指定的队列名一致(如default、email),否则消费端收不到 -
--sleep=3表示空闲时每 3 秒轮询一次,太小加重 Redis 压力,太大延迟高;--max-jobs=1000防止单进程跑太久内存泄漏,到数自动退出由 supervisor 拉起新进程 - 务必加
--env=production,避免开发环境配置(如日志级别、缓存驱动)被误加载
Redis 驱动下,failed_jobs 表没写入?先检查 retry_after 和 block_for
任务执行失败但没进 failed_jobs 表,不是队列没配好,而是 Redis 驱动默认行为和数据库驱动不同:它靠 retry_after 时间戳判断是否“超时失败”,而不是靠抛异常次数。
- ThinkPHP 的 Redis 队列配置里,
retry_after默认是 60 秒 —— 任务执行超过这个时间,哪怕没报错,也会被 Redis 标记为“已过期”,下次被其他 worker 拿走重试;真正失败(比如抛出异常)才进failed_jobs -
block_for控制BRPOP阻塞等待时长,设太短会导致频繁空轮询;建议和--sleep对齐,比如都设为 3 - 如果任务本身可能耗时较长(如导出大 Excel),必须调高
retry_after,同时确保max-jobs不太小,否则进程还没跑完就被 supervisor 杀掉
部署后任务不触发消费?优先查 QUEUE_CONNECTION 和 redis 连接池
本地测试正常,上线后任务一直挂在 jobs 表或 Redis list 里不动,90% 是环境变量或连接配置没对齐。
- 确认
.env中QUEUE_CONNECTION=redis(不是database),且REDIS_HOST、REDIS_PORT指向的是服务器上的 Redis 实例,不是127.0.0.1(Docker 场景下很常见) - 如果用了 Redis 连接池(如 predis),检查是否开启了
tcp keepalive;内网不稳定时,长连接容易静默断开,worker 却不报错,表现为“假死”——加--verbose启动看日志是否有Connection lost - Supervisor 的
environment字段必须显式透传PATH和HOME,否则某些扩展(如 pcntl)可能因找不到系统库而静默失效
最麻烦的其实是 PHP 进程模型和 Redis 连接生命周期的耦合:一个 queue:work 进程复用 Redis 连接,但连接断了不会自动重连,得靠 --max-jobs 重启来兜底。这点不像 Laravel 那样有内置重连机制,得自己用参数卡住边界。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











