Laravel 队列(Queue)系统从入门到实战:异步任务处理的完整指南

在现代 Web 应用开发中,有些操作需要耗费较长时间——发送邮件、生成报表、处理图片、调用第三方 API。如果让用户同步等待这些操作完成,体验会非常糟糕。Laravel 的队列系统正是为解决这类问题而生,它将耗时任务"推迟"到后台处理,让 HTTP 响应瞬间返回。

本文将从基础概念入手,逐步深入到 Laravel 队列系统的高级用法,包括驱动选择、任务编写、队列监控与调优,帮助你构建高性能的异步处理流水线。

队列系统的基本原理

Laravel 队列的核心思想非常简单:将需要执行的任务序列化后存入"队列"中,然后由独立的"工作者"(Worker)进程逐个取出并执行。

HTTP 请求 → 将任务推入队列 → 立即返回响应
                                    ↓
后台 Worker → 从队列取出任务 → 执行实际逻辑

这种架构带来的好处是显而易见的:

  • 响应速度提升:耗时操作不再阻塞用户请求
  • 失败重试:任务执行失败后可自动重试
  • 水平扩展:增加 Worker 进程数即可提升处理能力
  • 流量削峰:突发请求进入队列,Worker 按节奏处理

驱动选择与配置

Laravel 支持多种队列驱动,config/queue.php 是配置入口。

数据库驱动(Database)

最简单的持久化方案,无需额外服务:

php artisan queue:table
php artisan migrate

jobs 表结构包含 idqueue(队列名)、payload(序列化的任务)、attempts(重试次数)、reserved_at(保留时间)、available_at(可执行时间)、created_at 等字段。

Redis 驱动(推荐生产环境)

性能远超数据库驱动,适合高并发场景:

// config/queue.php
'default' => env('QUEUE_CONNECTION', 'redis'),

'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 90,
        'block_for' => null,
        'after_commit' => true,
    ],
],

需要安装 predis/predis 或 phpredis 扩展,并在 .env 中配置:

QUEUE_CONNECTION=redis
REDIS_QUEUE=default

其他驱动

  • sync:同步执行,不真正使用队列,用于本地开发和测试
  • SQS:AWS 的托管队列服务,无需自己维护
  • Beanstalkd:轻量级内存队列
  • RabbitMQ:功能完善的消息队列,需安装 laravel-queue-rabbitmq 扩展包

创建与分发任务

任务是队列的最小执行单元,通常继承 ShouldQueue 接口。

生成任务类

php artisan make:job ProcessOrder

生成的类位于 app/Jobs/ProcessOrder.php

<?php

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, SerializesModels;

    public function __construct(
        public Order $order
    ) {}

    public function handle(): void
    {
        // 执行订单处理逻辑
        $this->order->markAsProcessing();

        // 调用物流 API
        app(ShippingService::class)->createShipment($this->order);

        // 发送通知
        $this->order->user->notify(new OrderShipped($this->order));
    }
}

分发任务

在控制器或其他位置分发任务:

// 延迟执行
ProcessOrder::dispatch($order)->delay(now()->addMinutes(10));

// 指定队列
ProcessOrder::dispatch($order)->onQueue('high');

// 指定连接
ProcessOrder::dispatch($order)->onConnection('redis');

// 链式组合
ProcessOrder::dispatch($order)
    ->onQueue('high')
    ->onConnection('redis')
    ->delay(now()->addMinutes(5));

任务链与批处理

Laravel 支持按顺序执行任务链:

Bus::chain([
    new ProcessOrder($order),
    new SendOrderConfirmation($order),
    new UpdateInventory($order),
])->dispatch();

当需要批次处理大量数据时,使用批处理更合适:

$batch = Bus::batch([
    new ProcessOrder($order1),
    new ProcessOrder($order2),
    // ...
])->then(function (Batch $batch) {
    // 所有任务完成后的回调
})->catch(function (Batch $batch, Throwable $e) {
    // 有任务失败时的回调
})->finally(function (Batch $batch) {
    // 无论成功失败都会执行
})->dispatch();

return $batch->id;

通过 $batch->id 可在前端实时查询处理进度:

$batch = Bus::findBatch($batchId);
return response()->json([
    'progress' => $batch->progress(),
    'finished' => $batch->finished(),
    'failed' => $batch->failedJobs,
]);

运行队列 Worker

基本用法

# 启动默认队列的 Worker
php artisan queue:work

# 指定队列和连接
php artisan queue:work redis --queue=high,default

# 指定每次最多处理 3 个任务后退出(用于避免内存泄漏)
php artisan queue:work --max-jobs=1000

生产环境最佳实践

使用 Supervisor 守护 Worker 进程,确保其崩溃后自动重启。安装 Supervisor 后创建配置 /etc/supervisor/conf.d/laravel-worker.conf

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600

关键参数说明:

  • numprocs=8:启动 8 个 Worker 进程并行处理
  • --tries=3:每个任务最多重试 3 次
  • --sleep=3:无任务时休眠 3 秒
  • --max-time=3600:Worker 运行 1 小时后自动重启,防止内存泄漏

任务失败与重试机制

失败处理

执行 failed_jobs 表迁移:

php artisan queue:failed-table
php artisan migrate

当任务超出重试次数后,会写入 failed_jobs 表。可以在任务类中定义 failed 方法:

public function failed(\Throwable $e): void
{
    Log::error('订单处理失败', [
        'order_id' => $this->order->id,
        'error' => $e->getMessage(),
    ]);

    $this->order->markAsFailed();
    Notification::send(admin, new JobFailedNotification($this->order));
}

失败任务管理

# 查看所有失败任务
php artisan queue:failed

# 重试指定 ID 的任务
php artisan queue:retry 5

# 重试所有失败任务
php artisan queue:retry all

# 删除失败任务
php artisan queue:forget 5

# 清空所有失败任务
php artisan queue:flush

速率限制

避免调用外部 API 时触发限流:

public function handle(): void
{
    // 每秒最多处理 10 个任务
    Redis::throttle('api-calls')->allow(10)->every(1)->then(
        function () {
            // 执行业务逻辑
            $this->syncProducts();
        },
        function () {
            // 释放回队列,5 秒后重试
            $this->release(5);
        }
    );
}

队列优先级与分级

合理划分队列级别,确保重要任务优先处理:

// 高优先级队列:订单支付、即时通知
ProcessPayment::dispatch($payment)->onQueue('high');

// 默认队列:邮件发送、数据处理
SendInvoice::dispatch($invoice)->onQueue('default');

// 低优先级队列:日志清理、报表生成
GenerateReport::dispatch($report)->onQueue('low');

Worker 启动时按优先级处理:

php artisan queue:work --queue=high,default,low

Worker 会优先消费 high 队列,该队列为空后再处理 default,最后处理 low

事件监听与监控

Laravel 为队列系统提供了丰富的事件,可以在 AppServiceProvider 中注册监听器:

public function boot(): void
{
    Queue::before(function (JobProcessing $event) {
        Log::info('任务开始处理', [
            'job' => $event->job->resolveName(),
            'queue' => $event->job->getQueue(),
        ]);
    });

    Queue::after(function (JobProcessed $event) {
        // 任务处理完成
    });

    Queue::failing(function (JobFailed $event) {
        // 任务失败告警
        Alert::critical("任务失败: {$event->job->resolveName()}");
    });

    Queue::looping(function () {
        // 每次循环时的回调,可用于检查内存使用
        if (memory_get_usage() > 100 * 1024 * 1024) {
            exit; // Worker 自动退出,Supervisor 会重启
        }
    });
}

Horizon 监控面板

如果使用 Redis 驱动,强烈建议安装 Laravel Horizon——官方的队列监控面板:

composer require laravel/horizon
php artisan horizon:install

配置 config/horizon.php 后,启动 Horizon 代替 queue:work

php artisan horizon

Horizon 提供了一套美观的 Web 面板,实时展示队列吞吐量、等待时间、失败任务、Worker 状态等指标,还包括任务指标的趋势图表,非常适合生产环境的日常运维。

常见陷阱与最佳实践

1. 序列化注意事项

SerializesModels Trait 会在序列化时只保留模型的主键,反序列化时从数据库重新查询。这意味任务入列后如果模型数据发生变化,Worker 读取的是最新数据,而不是入列时的数据。如果需要快照数据,手动传递所需字段:

public function __construct(
    public int $orderId,
    public array $orderData,  // 手动传递数据快照
) {}

2. 避免长时间持有资源

handle 方法中开启的数据库连接、HTTP 客户端等资源要及时释放。使用 oncefinally 保证资源清理。

3. 任务唯一性

防止同一任务被重复入列,利用 Redis 实现唯一队列:

public function handle(): void
{
    $lock = Cache::lock("process-order-{$this->order->id}", 10);

    if (!$lock->get()) {
        $this->release(10);
        return;
    }

    try {
        // 执行处理逻辑
    } finally {
        $lock->release();
    }
}

4. 任务应该幂等

无论任务执行一次还是多次,结果应该一致。例如发送邮件前先检查是否已发送过,扣减库存时使用数据库原子操作。

5. 队列守护进程的优雅关闭

Supervisor 配置 stopwaitsecs=3600 确保 Worker 处理完当前任务后才退出,避免任务中断丢失。同时配合 queue:work --timeout=0 让 Worker 自己管理超时。

总结

Laravel 队列系统是构建高性能 Web 应用的基石。从最简单的数据库驱动到生产环境的 Redis + Horizon 组合,Laravel 提供了从入门到工业级的完整方案。

以一个典型的电商系统为例:

  • 用户下单 → ProcessOrder 入列 high 队列 → 响应返回"订单处理中"
  • Worker 处理订单,调用支付网关、库存扣减、物流对接
  • 全部完成后发送通知,用户实时收到结果

这套流程将原本需要 3-5 秒的同步操作压缩到 50ms 以内完成响应,显著提升了用户体验和系统吞吐量。

掌握队列系统,意味着你能够构建一种"异步优先"的架构思维——不再被同步阻塞束缚,而是以事件驱动的方式编排业务流程。这正是现代后端开发的核心能力之一。