引言
发送邮件、生成报表、调用第三方 API——这些「慢但必要」的事如果同步做,接口就慢了。队列把它们扔到后台异步处理,让接口秒回。Laravel 的队列体系(Job + Worker + 驱动)和任务调度(Scheduler)是每个生产应用的地基。本文从架构讲起,覆盖 Job 定义、失败重试、幂等、调度与监控。
前置:/php-laravel-internals/(Laravel 内核)、/php-caching-redis/(Redis)、/php-microservices-message-queue/(MQ 对比)。
目录
- 1. 为什么需要队列
- 2. 队列架构与驱动
- 3. 定义 Job 与分发
- 4. 队列、延迟与优先级
- 5. Worker 与进程管理
- 6. 失败重试与死信处理
- 7. 幂等:重复执行的防线
- 8. 任务调度 Scheduler
- 9. 分布式调度锁与监控
- 10. 速查表与一句话记忆
- 延伸阅读
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 |
| 定义 Job | ShouldQueue + handle |
| 分发 | dispatch / onQueue / delay |
| Worker | queue: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 调度文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。