8.1 队列与任务:异步施工队


8.1 队列与任务:异步施工队

本节摘要:队列把"记单"与"干活"分离:请求把任务对象推进队尾就返回,后台的 worker 进程逐单消费。本节讲清任务类的封装规范(哪些能进任务、哪些绝不能)、驱动选型(数据库与 Redis)、延迟与优先级、失败重试与兜底,最后以"登记工单后发通知"的完整改造为例,给接口做一次立竿见影的提速。

从一次三秒的提交说起

第六章的建单表单上线后收到反馈:提交后要等两三秒才跳转。旁听发现大头是 SMTP 发信——同步等远端邮件服务器响应。发邮件对用户无感知价值(晚十秒收到也一样),却扣着请求不放。这类"结果不影响本次响应"的活,就是队列的目标客户。

任务类:把活封装成可传递的单据

php artisan make:job SendOrderNotification
<?php namespace App\Jobs; use App\Models\WorkOrder; use App\Notifications\OrderCreated; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class SendOrderNotification implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; // 公开属性即任务参数:序列化存入队列,消费端反序列化恢复 public function __construct(public WorkOrder $order) {} // 重试参数:最多三次,每次失败指数退避(秒) public int $tries = 3; public array $backoff = [10, 60, 300]; public function handle(): void { $this->order->team->notify(new OrderCreated($this->order)); } }
<?php // 控制器侧:从"当场干完"改成"记单走人" public function store(StoreOrderRequest $request): RedirectResponse { $order = WorkOrder::create($request->validated()); SendOrderNotification::dispatch($order); // 推进队列,立即返回 return redirect()->route('orders.show', $order) ->with('status', '工单已登记,通知正在路上'); }

注意 __construct 里传的是模型对象而不是编号:SerializesModels 这个 trait 会在入队时把模型序列化成"编号标识",出队时按编号重新查库。这带来一个必须遵守的纪律——任务执行时数据以出队那一刻的库为准,构造任务那一刻的状态不会自动冻结。

图 8-1:同步施工与异步施工的对比流水线

图 8-1:同步施工与异步施工的对比流水线

驱动选型与运行

驱动决定"单据堆在哪"。开发期 sync(当场执行,便于断点调试)或 database(一张 jobs 表,零额外组件);生产期 Redis(内存队列,吞吐高,生态成熟);跨语言的重量级场景考虑 SQS 这类云服务。驱动在 .env 里一行切换,任务代码零改动——这就是第八章反复强调的"面向接口"红利。

# 运行消费进程(开发期直接跑;生产期用 supervisor 守护并开机自启) php artisan queue:work --queue=high,default --tries=3 --backoff=10,60,300 # 部署新代码后必须平滑重启 worker(旧进程还驻着旧代码) php artisan queue:restart

⚠️ 常见坑清单:部署后忘了 queue:restart,线上一半任务跑的还是老逻辑;任务里用了请求态(auth()->user()),队列进程里没有请求,直接炸——上下文在入队前取好塞进任务参数;send 邮件对象闭包里捕获大对象,导致任务体积膨胀,入队只传编号等小件;失败任务要盯 failed_jobs 表,queue:retry all 批量重放。

优先级与延迟同样常用:多队列加 --queue=high,default 让支付回调这类高优单插队;dispatch($job)->delay(now()->addMinutes(10)) 做延迟单(典型如"半小时后未支付自动关单",配合第八章第二节的事件广播效果更好)。

事务、幂等与监控:三个必须回应的问题

入队写在事务里安全吗?
默认有竞态:事务还没提交,队列进程可能已经把任务捞走执行,结果任务处理的记录根本查不到。解法是 afterCommit——dispatch($job)->afterCommit(),让任务等事务提交后才真正入队。反过来还有 beforeCommit 与"事务回滚任务自动取消"的语义,都属于"队列与数据库握手"的知识面,涉账场景务必掌握。

任务重试会不会把同一件事干两遍?
会,所以任务要写成幂等的:发通知前查一下通知记录在不在,扣款前核对流水状态,重试时发现"已经干过"就静默返回。把每次执行都当成"可能是第二次"来写,重试机制才真正安全。

怎么知道队列堵了?
queue:monitor redis,default --max=1000 检查队列深度,超阈值以非零退出码报警,挂进第九章的调度告警即可;worker 停摆则靠 supervisor 的进程监控兜底。两条腿都有,后台才敢说"可信"。

本节要点回顾

  • 判据一句话:活的结果不影响本次响应,就入队。
  • 任务规范:参数只传小件与编号,SerializesModels 管模型取新值,tries 加 backoff 定重试。
  • 驱动可切换:开发 sync 或 database,生产 Redis,任务代码零改动。
  • 运维三件事:supervisor 保活、部署后 queue:restart、失败表定期清理重放。

队列流水线转起来了。下一节补齐车间的时间维度:到点自动巡检的调度器,以及让全工地知道"有单子动过了"的事件与通知。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U