
Laravel通过队列异步处理长耗时任务,禁止在控制器中同步执行;需封装为可中断、可重试、可监控的Job类,配合Supervisor守护worker进程,并合理配置max-jobs、max-time、重试机制与队列分发策略。
Laravel 本身不直接“处理”长耗时任务,而是通过 queue 将其异步移交到后台工作进程;Supervisor 不是 Laravel 内置组件,它只是 Linux 上稳定守护 php artisan queue:work 进程的通用工具——用错位置或配置不当,队列照样卡死、任务丢失、内存爆满。
PHP-FPM 进程会阻塞,Web 请求超时(Nginx 默认 60s,Apache 类似),用户看到 504;同时数据库连接可能被回收、日志写入中断、异常无法被捕获上报。真正要跑的不是“代码”,是“可中断、可重试、可监控”的队列任务。
必须把耗时逻辑封装进 App\Jobs\YourLongRunningJob,然后用 dispatch() 推进队列:
use App\Jobs\ProcessMonthlyReport;
ProcessMonthlyReport::dispatch($userId)->onQueue('reports');
关键点:
onQueue('reports') 显式指定队列名,避免所有任务挤在 default 队列导致优先级混乱$this->user = auth()->user() 这类运行时依赖——序列化时会失败;改用传 ID,handle() 中再查__construct() 中做 DB 查询或 HTTP 请求,只存原始参数常见原因不是代码报错,而是 Supervisor 配置没关掉自动重启策略,或 Laravel 的 QUEUE_WORKER_MAX_JOBS / QUEUE_WORKER_MAX_TIME 触发了优雅退出。默认 php artisan queue:work 每处理 250 个任务或运行 60 分钟就会退出——这是防止内存泄漏的保护机制,不是 bug。
Supervisor 需显式配置为“自动拉起退出的进程”,且禁用 autostart=false 以外的干扰项:
[program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/your-app/artisan queue:work --queue=reports,default --sleep=3 --tries=3 --max-jobs=250 --max-time=3600 autostart=true autorestart=true user=www-data numprocs=2 redirect_stderr=true stdout_logfile=/var/log/supervisor/worker.log
注意:
--max-jobs=250 和 --max-time=3600 要和 Laravel 的 config/queue.php 中 options 里的值对齐,否则 supervisor 看不到进程退出原因numprocs=2 表示起两个独立 worker,但它们共享同一个 --queue=reports,default,Laravel 内部会轮询分发,不是手动负载均衡--sleep=3:空闲时每 3 秒轮询一次 Redis/DB,太小加重 IO,太大响应延迟先看 redis-cli -a yourpass llen queues:default,如果数字持续 > 1000,说明消费跟不上生产。不是加 worker 数量就能解决——更可能是单个任务执行太久(比如一个 PDF 生成要 80 秒),堵住整个队列。
排查路径:
php artisan queue:failed 查失败任务,重点看 exception 字段是否含 TimeoutException 或 PDOException: Lost connection
file_get_contents、curl_exec)没设超时,应强制加 timeout=10
DB::listen() 记录慢查询,>500ms 的必须优化sleep() 等待外部结果——改用「状态轮询 + delayed dispatch」模式例如第三方 API 返回 processing 状态,就不要 while 循环等,而应 dispatch 自己带 delay:
if ($status === 'processing') {
self::dispatch($jobId)->delay(n
ow()->addSeconds(30));
}
Supervisor 只管进程不死,不管任务逻辑是否合理;队列积压本质是业务节奏和 worker 吞吐不匹配,或是任务设计违反了异步原则。最常被忽略的一点:没有给每个关键 job 加 public $tries = 3; 和 public $backoff = 60;,导致失败后立即重试,雪崩式打垮下游服务。