Laravel每分钟执行的调度任务重复触发Stripe转账API问题求助
解决方案
- 首先给调度任务添加防重叠配置,修改
Kernel.php中的调度代码:
$schedule->command('charge:debts')->everyMinute()->withoutOverlapping();
withoutOverlapping是Laravel调度自带的防任务重叠机制,会避免上一次任务未执行完成时新的任务进程启动,从源头避免多进程同时处理同一条数据的问题。
- 给待处理债务记录查询加悲观锁+事务,同时为Stripe请求添加幂等Key,避免并发冲突和重复扣款,修改业务代码如下:
// 开启数据库事务 DB::beginTransaction(); try { // 查询时加行锁,其他进程无法读取当前匹配的待处理行,直到当前事务提交 $active_debts = Debt::where([['status', '=', 0]])->with('channels')->lockForUpdate()->get(); foreach($active_debts as $debt) { $stripe = new \Stripe\StripeClient(env('STRIPE_CODE')); $stripe->transfers->create([ 'amount' => $transfer_amount, 'currency' => $currency, 'destination' => 'XXXXXX',//acct_1GqQxFH4ie1ley5J 'transfer_group' => 'Pltaform fees', ], [ 'stripe_account' => $merchant_code, // 新增幂等Key,用债务ID作为唯一标识,就算重复发起请求Stripe也只会执行一次转账 'idempotency_key' => 'debt_transfer_' . $debt->id ]); Debt::where('id', $debt->id)->update([ 'status' => 1 ]); } DB::commit(); } catch(\Stripe\Exception\InvalidRequestException $e) { DB::rollBack(); // 原有异常处理逻辑 }
- 可选优化:新增处理中中间状态,查询到待处理债务后先将状态更新为
处理中(比如定义status=2为处理中),再发起Stripe请求,请求成功后更新为成功状态,失败后回滚为待处理或标记为失败,进一步缩小并发冲突的时间窗口。
内容的提问来源于stack exchange,提问作者Adrijus Jakučionis
相关产品推荐
相关产品推荐

