ThinkPHP队列必须禁用sync驱动、显式配置Redis连接与retry_after超时阈值,并用supervisor守护worker进程;否则任务将同步执行、失败不记录、延迟不准或无限重试。

你需要在PHP框架中稳定运行邮件发送、报表生成这类耗时任务,又不想让用户等十几秒才看到页面响应——队列任务与异步处理就是解决这个问题的核心机制,但不同框架的实现逻辑差异极大,选错驱动、配错参数或漏掉守护进程,任务就会静默失败或无限重试。
ThinkPHP队列必须改掉默认sync驱动
调用Queue::push()后页面秒回、日志没输出、数据库没变化,不是代码写错了,而是config/queue.php里'default' => 'sync'还在生效——这会让任务当场同步执行,且不记录、不捕获异常、delete()和release()全部失效。
打开config/queue.php,把'default'值从'sync'改成'redis'或'database'。
【必须同时配置connections.redis下的host、port、database三项,ThinkPHP 8.0+不再读取predis全局配置】
立即学习“PHP免费学习笔记(深入)”;
改完立刻测试:在任务里加sleep(3),刷新页面。如果HTTP响应时间仍为毫秒级,说明还没切成功;如果卡住3秒,证明仍在sync模式下假异步。
Laravel队列启动必须用supervisor守护
php artisan queue:work命令只能前台运行,关掉终端就停了,生产环境绝不能手动执行。
第一步:安装supervisor并创建配置文件/etc/supervisor/conf.d/laravel-worker.conf
第二步:写入以下内容(注意修改project路径和user):
[program:laravel-worker]
command=php /var/www/project/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/project/storage/logs/worker.log
第三步:执行sudo supervisorctl reread → sudo supervisorctl update → sudo supervisorctl start laravel-worker:*
这一步漏掉任何一环,队列就会断连——比如autorestart设成false,worker崩溃后任务永久积压;numprocs设太高又会吃光内存。
Redis驱动下retry_after是生死线
方法一:显式设置超时阈值
在config/queue.php的connections.redis.retry_after中写死数值,比如90或120。这个值不是重试间隔,而是单个任务允许的最大执行时间。发短信实际耗时85秒,retry_after却设成60,任务还没完成就被Redis标记为失败,立刻重入队列→重复发送两次。
方法二:动态适配任务类型
对高耗时任务(如导出百万行Excel),单独用Queue::connection('redis_long')->later(300, new ExportJob()),并在connections.redis_long里设retry_after=300,避免和普通邮件任务共用同一套超时策略。
【不要依赖ThinkPHP默认的60秒retry_after,它只适合极简场景】
失败任务必须主动记录,Redis不自动存日志
Database驱动会自动建failed_jobs表并写入错误堆栈,但Redis驱动默认什么都不留——你根本不知道哪个任务崩了、在哪一行报错。
监听JobFailed事件,在app/Providers/EventServiceProvider.php中注册:
protected $listen = [
'Illuminate\Queue\Events\JobFailed' => [
'App\Listeners\LogFailedJob',
],
];
然后在LogFailedJob@handle里把$event->exception->getTraceAsString()写进本地日志或ELK。
或者退而求其次:启动时加--tries=3 --delay=10参数,至少让框架重试三次再放弃,但依然看不到原始错误。



















