Laravel 队列与任务调度实战:Job、驱动、失败重试与 Cron

Laravel 队列与任务调度实战:队列架构与驱动(sync/redis/database)、定义 Job 与队列选择、延迟与优先级、失败重试与死信处理、幂等与幂等保证、队列监控、任务调度 Scheduler(Cron 替代)、分布式下调度锁、实战案例。

引言

发送邮件、生成报表、调用第三方 API——这些「慢但必要」的事如果同步做,接口就慢了。队列把它们扔到后台异步处理,让接口秒回。Laravel 的队列体系(Job + Worker + 驱动)和任务调度(Scheduler)是每个生产应用的地基。本文从架构讲起,覆盖 Job 定义、失败重试、幂等、调度与监控。

前置:/php-laravel-internals/(Laravel 内核)、/php-caching-redis/(Redis)、/php-microservices-message-queue/(MQ 对比)。


目录


1. 为什么需要队列

1.1 同步慢操作的问题

// 同步:发邮件/生成报表会阻塞接口几十秒
public function store(Request $request)
{
    // ...保存订单...
    $this->sendConfirmationEmail($order);   // 卡在这里,用户等!
    return response()->json($order);
}

1.2 队列解决什么

接口秒回:慢操作异步执行
削峰填谷:突发请求排队处理
解耦:主流程不依赖慢服务的成败

记忆:队列把「慢但必要」的操作用到后台,接口秒回 + 削峰 + 解耦——生产应用的异步地基。


2. 队列架构与驱动

2.1 架构

应用 → 把 Job 推入队列(sync/redis/db)→ Worker 进程不断拉取执行

2.2 驱动对比

驱动速度可靠性场景
sync同步立即-开发/测试
database中好小型应用
redis快好生产常用
SQS/Beanstalk外部好云/AWS

2.3 配置

// config/queue.php 默认驱动
'default' => env('QUEUE_CONNECTION', 'redis'),

记忆:队列驱动——开发用 sync、小应用 database、生产推荐 redis(快且可靠);QUEUE_CONNECTION 环境变量切换。


3. 定义 Job 与分发

3.1 生成与定义

php artisan make:job SendConfirmationEmail
class SendConfirmationEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public Order $order) {}
    public function handle(): void
    {
        Mail::to($this->order->user->email)->send(new OrderConfirmation($this->order));
    }
}

3.2 分发

// 分发(异步)
SendConfirmationEmail::dispatch($order);
// 同步执行
SendConfirmationEmail::dispatchSync($order);
// 延迟
SendConfirmationEmail::dispatch($order)->delay(now()->addMinutes(10));

记忆:Job = implements ShouldQueue + handle 方法 + dispatch() 分发;SerializesModels 自动序列化模型,dispatchSync 同步跑。


4. 队列、延迟与优先级

4.1 队列隔离

不同任务放不同队列,Worker 可按优先级消费:

public function queue()
{
    return match (true) {
        $this->order->isUrgent()  => 'high',
        default                    => 'default',
    };
}
// 分发到指定队列
SendEmail::dispatch($order)->onQueue('emails');

4.2 优先级与限速

// 高优队列先处理
// 限速:防第三方 API 被秒杀
RateLimiter::attempt('send-mail', 10, fn () => $this->release(10));

记忆:队列隔离用 onQueue / 方法 queue() 返回队列名;高优先级队列 Worker 先消费,限速用 RateLimiter。


5. Worker 与进程管理

5.1 启动 Worker

php artisan queue:work redis --queue=high,default --tries=3 --timeout=60

5.2 常驻与进程管理

  • queue:work:常驻进程,手动管理
  • queue:listen:每任务重启,开发用
  • 生产用 supervisor 守护 worker 进程
; /etc/supervisor/conf.d/queue.conf
[program:queue-worker]
command=php artisan queue:work redis --sleep=3 --tries=3
process_name=%(program_name)s_%(process_num)02d
numprocs=8          ; 8 个并发 worker

记忆:queue:work 常驻执行、queue:listen 开发用;生产用 supervisor 守护多进程 worker(numprocs 控制并发)。


6. 失败重试与死信处理

6.1 重试机制

// --tries=3:最多尝试 3 次,每次失败按 backoff 递增等待
php artisan queue:work --tries=3 --backoff=5

6.2 失败 Job 处理

public function failed(Throwable $e): void
{
    // 记录失败原因、通知负责人
    Log::error('job failed', ['order' => $this->order->id, 'e' => $e->getMessage()]);
}
php artisan queue:failed      # 查看失败任务
php artisan queue:retry 5     # 重试指定任务
php artisan queue:flush       # 清空失败表

记忆:–tries 限重试次数、–backoff 指数退避;failed() 记录失败并通知、queue:failed 查看、queue:retry 重试。


7. 幂等:重复执行的防线

7.1 为什么需要幂等

Worker 崩溃、网络抖动会导致 Job 重复执行——发邮件重复、扣款重复就是事故。幂等保证「执行两次 = 执行一次」。

7.2 实现手段

public function handle(): void
{
    // ① 唯一键去重(Redis SETNX)
    if (!Redis::set('job:'. $this->order->id . ':email', 1, 'EX', 3600, 'NX')) {
        return;   // 已执行过
    }
    Mail::to(...)->send(...);
    // ② 数据库唯一约束 / 幂等表
    Idempotency::firstOrCreate(['order_id' => $this->order->id], ...);
}

7.3 判断标准

写操作(扣款/发通知/入库)必须有幂等
读/无副作用操作天然幂等

记忆:幂等 = 重复执行无害——扣款/发邮件等写操作必须幂等;用 Redis SETNX 唯一键或数据库唯一约束去重。


8. 任务调度 Scheduler

8.1 声明式 Cron 替代

不用系统 Cron,在 routes/console.php 里声明:

use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')->dailyAt('03:00');
Schedule::command('cache:clear')->hourly();
Schedule::job(new SendDigest)->weekly()->monday();
Schedule::call(fn () => DB::table('t')->delete())->everyTenMinutes();

8.2 唯一系统 Cron 入口

# crontab 只放这一条,Laravel 内部调度
* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1

记忆:Scheduler 在 routes/console.php 声明任务(dailyAt/hourly/job),系统 crontab 只放一条 schedule:run 入口——所有定时任务声明式管理。


9. 分布式调度锁与监控

9.1 防重复执行(多实例部署)

多实例跑 schedule:run 会重复执行;用 withoutOverlapping 加锁:

Schedule::command('report:generate')
    ->dailyAt('03:00')
    ->withoutOverlapping();   // 上次没跑完则跳过

9.2 监控

php artisan queue:monitor          # 监控队列长度
# 结合失败队列、Redis 队列长度、worker 存活做告警
php artisan schedule:list          # 查看所有调度任务
php artisan schedule:test          # 测试调度

记忆:多实例用 withoutOverlapping 防重复执行;queue:monitor/schedule:list 监控队列与调度,结合失败队列/长度告警。


10. 速查表与一句话记忆

场景做法
驱动sync/database/redis
定义 JobShouldQueue + handle
分发dispatch / onQueue / delay
Workerqueue:work + supervisor
重试–tries / failed()
幂等Redis SETNX 唯一键
调度Schedule::command
防重复withoutOverlapping

一句话记忆:Laravel 队列 = 把慢操作扔后台(ShouldQueue Job + dispatch + redis 驱动),Worker 常驻执行(supervisor 守护多进程);失败用 –tries 重试 + failed() 记录;写操作必须幂等(Redis SETNX 唯一键);定时任务用 Scheduler 在 routes/console.php 声明、系统只放一条 schedule:run、多实例 withoutOverlapping 防重复——异步化让接口秒回、削峰解耦,是生产应用的地基。


延伸阅读

  • /php-laravel-internals/ — Laravel 请求生命周期
  • /php-caching-redis/ — Redis 缓存与锁
  • /php-microservices-message-queue/ — RabbitMQ/Kafka 外部 MQ
  • /php-testing-laravel-feature/ — Queue::fake 测试
  • /php-laravel-eloquent-advanced/ — 模型层与 Job 协作
  • [[redis]] — Redis 深度专题
  • [[devops]] — 运维与进程管理
  • Laravel 队列文档
  • Laravel 调度文档

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. PHP 面向对象与设计模式:SOLID、常用模式与 Laravel 实践
  2. PHP 静态分析与代码质量:PHPStan、Psalm、Rector 与 CI 门禁
  3. PHP 部署运维实战:Nginx、PHP-FPM、Docker 与 CI/CD