sen*_*nty 3 php deadlock laravel eloquent
我有一个取消订单的方法可以退款给用户。
但是,使用 API,如果用户为同一记录调用端点两次(在循环中),它会向用户退款两次。如果我一次尝试 3 次 Api 调用,前 2 个请求会获得退款,第三个请求不会。
public function cancelOrder($orderId) {
// First I tried to solve with cache,
// but it is not fast enough to catch the loop
if (Cache::has("api-canceling-$orderId")) {
return response()->json(['message' => "Already cancelling"], 403);
}
Cache::put("api-voiding-$labelId", true, 60);
// Here I get the transaction, and check if 'transaction->is_cancelled'.
// I thought cache will be faster but apparently not enough.
$transaction = Transaction::where('order_id', $orderId)
->where('user_id', auth()->user()->id)
->where('type', "Order Charge")
->firstOrFail();
if ($transaction->is_cancelled) {
return response()->json(['message' => "Order already cancelled"], 403);
}
// Here I do the api call to 3rd party service and wait for response
try {
$result = (new OrderCanceller)->cancel($orderId);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 403);
}
$transaction->is_cancelled = true;
$transaction->save();
// This is the operation getting called twice.
Transaction::createCancelRefund($transaction);
return response()->json(['message' => 'Refund is made to your account']);
}
Run Code Online (Sandbox Code Playgroud)
该createCancelRefund()方法如下所示:
public static function createCancelRefund($transaction) {
Transaction::create([
'user_id' => $transaction->user_id,
'credit_movement' => $transaction->credit_movement * -1,
'type' => "Order Refund"
]);
}
Run Code Online (Sandbox Code Playgroud)
我尝试过的事情:
将cancelOrder()方法中的所有内容包装到DB::transaction({}, 5)闭包中。(也试过DB::beginTransaction()方法)
使用->lockForUpdate()上$transaction = Transaction::where('order_id', $orderId)...查询。
将createCancelRefund()内容包装在里面DB::transaction({}, 5),但我认为create()它没有帮助。
尝试使用缓存,但没有那么快。
查找节流,但似乎不能阻止这种情况(如果我说 2 个请求/分钟,仍然会发生重复创建)
防止内部重复退款创建的正确方法是createCancelRefund()什么?
原子锁解决了我的问题。
原子锁允许操作分布式锁而无需担心竞争条件。例如,Laravel Forge 使用原子锁来确保一次在服务器上只执行一个远程任务
public function cancelOrder($orderId) {
return Cache::lock("api-canceling-{$orderId}")->get(function () use ($orderId) {
$transaction = Transaction::where('order_id', $orderId)
->where('user_id', auth()->user()->id)
->where('type', "Order Charge")
->firstOrFail();
if ($transaction->is_cancelled) {
return response()->json(['message' => "Order already cancelled"], 403);
}
try {
$result = (new OrderCanceller)->cancel($orderId);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 403);
}
$transaction->is_cancelled = true;
$transaction->save();
// This is the operation getting called twice.
Transaction::createCancelRefund($transaction);
return response()->json(['message' => 'Refund is made to your account']);
});
return response()->json(['message' => "Already cancelling"], 403);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1761 次 |
| 最近记录: |