Laravel如何处理长耗时任务_Laravel配置Supervisor进程管理监控队列【指南】

12次阅读

laravel通过队列异步处理长耗时任务,禁止在控制器中同步执行;需封装为可中断、可重试、可监控的Job类,配合Supervisor守护worker进程,并合理配置max-jobs、max-time、重试机制与队列分发策略。

Laravel如何处理长耗时任务_Laravel配置Supervisor进程管理监控队列【指南】

Laravel 本身不直接“处理”长耗时任务,而是通过 queue 将其异步移交到后台工作进程;Supervisor 不是 Laravel 内置组件,它只是 linux 上稳定守护 php artisan queue:work 进程的通用工具——用错位置或配置不当,队列照样卡死、任务丢失、内存爆满。

为什么不能直接在控制器里跑 sleep(300) 或大循环

PHP-FPM 进程会阻塞,Web 请求超时(nginx 默认 60s,apache 类似),用户看到 504;同时数据库连接可能被回收、日志写入中断、异常无法被捕获上报。真正要跑的不是“代码”,是“可中断、可重试、可监控”的队列任务。

必须把耗时逻辑封装进 appJobsYourLongRunningJob,然后用 dispatch() 推进队列:

use AppJobsProcessMonthlyReport; ProcessMonthlyReport::dispatch($userId)->onQueue('reports');

关键点:

  • onQueue('reports') 显式指定队列名,避免所有任务挤在 default 队列导致优先级混乱
  • Job 类里不要用 $this->user = auth()->user() 这类运行时依赖——序列化时会失败;改用传 ID,handle() 中再查
  • 避免在 __construct() 中做 DB 查询或 http 请求,只存原始参数

queue:work 进程为什么总自己退出?

常见原因不是代码报错,而是 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.phpoptions 里的值对齐,否则 supervisor 看不到进程退出原因
  • numprocs=2 表示起两个独立 worker,但它们共享同一个 --queue=reports,default,Laravel 内部会轮询分发,不是手动负载均衡
  • 别漏掉 --sleep=3:空闲时每 3 秒轮询一次 redis/DB,太小加重 IO,太大响应延迟

Redis 队列爆满、任务积、retry 堆成山怎么办?

先看 redis-cli -a yourpass llen queues:default,如果数字持续 > 1000,说明消费跟不上生产。不是加 worker 数量就能解决——更可能是单个任务执行太久(比如一个 pdf 生成要 80 秒),堵住整个队列。

排查路径:

  • php artisan queue:failed 查失败任务,重点看 exception 字段是否含 TimeoutExceptionPDOException: Lost connection
  • 检查任务中是否有同步 HTTP 调用(file_get_contentscurl_exec)没设超时,应强制加 timeout=10
  • DB 查询是否缺索引?DB::listen() 记录慢查询,>500ms 的必须优化
  • 避免在 job 中调 sleep() 等待外部结果——改用「状态轮询 + delayed dispatch」模式

例如第三方 API 返回 processing 状态,就不要 while 循环等,而应 dispatch 自己带 delay:

if ($status === 'processing') {     self::dispatch($jobId)->delay(now()->addSeconds(30)); }

Supervisor 只管进程不死,不管任务逻辑是否合理;队列积压本质是业务节奏和 worker 吞吐不匹配,或是任务设计违反了异步原则。最常被忽略的一点:没有给每个关键 job 加 public $tries = 3;public $backoff = 60;,导致失败后立即重试,雪崩式打垮下游服务。

text=ZqhQzanResources