核心原因是retry_after小于实际任务耗时、supervisor配置不当及redis失败无日志。需调大retry_after、用supervisor替代--daemon并配全参数、手动监听jobfailed事件、确保数据库连接稳定、修改后重启supervisor。

ThinkPHP 5.1 队列消费进程无故退出,导致任务被反复标记为“失败”甚至重复入队,核心原因不是代码出错,而是运行环境、配置策略与守护机制不匹配。关键要切断“假失败”链路,让任务真正执行完再退出,失败时也能留痕可查。
检查 retry_after 是否小于实际任务耗时
这是最常见也最隐蔽的诱因。retry_after 不是重试间隔,而是单次任务允许执行的最长时间。Redis 驱动靠它判断任务是否“超时失败”。默认值 60 秒极易触发误判。
- 若发短信或导出报表类任务实际耗时 75 秒,但 retry_after=60,第 61 秒框架就会强制把正在跑的任务标记失败,并立刻重推进队列——你看到的是“重复执行”,本质是“还没跑完就被砍了”
- 必须在 config/queue.php → connections.redis.retry_after 中显式设为预估最大耗时 × 1.5,例如 90 或 120
- 上线后配合日志或监控(如记录任务 start/end 时间戳)验证真实执行时长,再动态调整
用 Supervisor 替代 --daemon,且配全关键参数
--daemon 模式会让 PHP 进程长期驻留,内存持续增长,几小时后可能被系统 OOM killer 强杀,Supervisor 只看到“退出码 -9”,无法定位原因。正确做法是让每次任务执行完就退出,由 Supervisor 拉起新进程。
- Supervisor 的 .ini 配置中,command 必须去掉 --daemon,写成:php /var/www/tp5/public/index.php think queue:work redis --queue=default --tries=3 --memory=128 --sleep=3
- 必须设置:stopwaitsecs=120(大于最长任务耗时),否则 SIGTERM 发出后等不及任务结束就被 SIGKILL 强杀
- 加上 killasgroup=true 和 stopasgroup=true,防止子进程残留
- 所有输出重定向到日志:stdout_logfile=/var/log/tp-queue.log,并在 command 末尾加 2>&1
确保失败任务能被记录和追踪
Redis 驱动默认不写 failed_jobs 表,也不触发 JobFailed 事件监听——这意味着任务崩了你也看不到任何痕迹。
- 不要依赖 “failed.driver => database” 配置来捕获 Redis 失败;它对 Redis 驱动无效
- 必须在 app/event.php 或服务提供者中手动监听 think\queue\Event::JobFailed,并在回调里至少记录:$event->job->getName()、$event->exception->getMessage() 和完整堆栈
- 任务 handle() 方法中,成功后调用 $job->delete(),临时失败调用 $job->release(60),避免任务滞留或无限循环
- 禁用全局 try/catch 吞异常:如果在 fire() 最外层 catch 了所有错误却不 throw,框架会认为“执行成功”,跳过失败处理逻辑
验证数据库连接与进程生命周期
work 进程复用数据库连接,空闲超 MySQL wait_timeout(通常 8 小时)后,下次查询直接报 “MySQL server has gone away”,但进程不退出,后续任务静默失败。
- 启动命令中加入 --once 参数做单次测试,确认连接是否稳定;不稳定则需在任务中主动 reconnect 或改用短连接
- 检查 Supervisor 是否存在多个残留进程:ps aux | grep queue:work,有残留需先 kill 再 start
- 修改任务类后,必须重启 Supervisor 进程(supervisorctl restart tp-queue),否则仍在跑旧代码
- 确认 runtime/log/ 目录可写,且 config/app.php 中 log.level 包含 error,否则失败日志根本不会落地
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











