Laravel 队列与 Job:订单超时关闭的异步化与去重锁
test12026-09-112 次阅读
下单后延时关单不该阻塞请求
电商里常见一个需求:用户下单后 30 分钟未支付就自动关闭订单。最直接的写法是在下单接口里 sleep 30 分钟再处理——这显然不行,会占住一个 PHP 进程直到天荒地老。正确做法是把"关闭"这件事丢进队列,用队列的延迟投递能力在 30 分钟后触发,下单接口立刻返回。
定义一个可延迟的 Job
Laravel 的 Job 只要 implements ShouldQueue,dispatch 时就能走队列。用 ->delay() 指定延时,用 ->onQueue() 指定队列名以便单独调worker。
class CloseExpiredOrder implements ShouldQueue
{
use Queueable, InteractsWithQueue, SerializesModels;
public function __construct(public int $orderId) {}
public function handle(OrderService $svc)
{
$svc->closeIfUnpaid($this->orderId);
}
}
// 下单成功后投递,30 分钟后执行
CloseExpiredOrder::dispatch($order->id)
->delay(now()->addMinutes(30))
->onQueue('order');
用缓存锁防止重复关闭
队列可能因为重试、重复投递、worker 并发而多次执行同一个 Job。关闭订单这类操作必须幂等。下面用 Cache::lock 加一个短期锁,拿不到锁就直接返回,等于这次执行被忽略。
public function handle(OrderService $svc)
{
$lock = Cache::lock("order:close:{$this->orderId}", 10);
if (!$lock->get()) {
return;
}
$svc->closeIfUnpaid($this->orderId);
}
失败与重试也要讲策略
- 网络抖动的失败交给队列自带的重试,在 Job 上设 $tries = 3 即可。
- 超时未支付的正常流程不是"失败",不要让它进重试,锁已经兜底幂等。
- 用 php artisan queue:work --queue=order 单独起 worker,避免关单任务被大批量导入任务拖慢。
把耗时和延时逻辑搬进队列之后,下单接口恢复成纯同步的写库加投递,响应时间从"可能卡 30 分钟"变成几十毫秒。队列的本质是把"现在要做"和"现在能做完"解耦开,而锁保证了解耦之后结果依然正确。
T
test1
文章作者
下单后延时关单不该阻塞请求 电商里常见一个需求:用户下单后 30 分钟未支付就自动关闭订单。最直接的写法是在下单接口里...
- 分类
- 技术
- 发布时间
- 2026-09-11
- 字数
- 约 1230 字
- 阅读
- 2 次
