У меня есть метод отмены заказа, который возвращает пользователю.
Однако при использовании API, если пользователь вызывает конечную точку дважды (в цикле) для одной и той же записи, он дважды возвращает пользователю деньги. Если я попробую 3 вызова Api одновременно, первые 2 запроса получат возмещение, 3-й запрос - нет.
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']);
}
Метод createCancelRefund() выглядит так:
public static function createCancelRefund($transaction) {
Transaction::create([
'user_id' => $transaction->user_id,
'credit_movement' => $transaction->credit_movement * -1,
'type' => "Order Refund"
]);
}
Что я пробовал:
Оборачиваем все внутри cancelOrder() метода в DB::transaction({}, 5) замыкание. (Также попробовал DB::beginTransaction() подход)
Использование ->lockForUpdate() в $transaction = Transaction::where('order_id', $orderId)... запросе.
Оборачиваю createCancelRefund() контент внутрь DB::transaction({}, 5), но я думаю, что это create() не помогло.
Пробовал использовать кеш, но не так быстро.
Посмотрел на дросселирование, но не похоже, что это может предотвратить это (если я скажу 2 запроса/мин, создание дубликатов все еще происходит)
Как правильно предотвратить дублирование создания возвратов внутри createCancelRefund()?






Atomic Lock решил мою проблему.
Атомарные блокировки позволяют манипулировать распределенными блокировками, не беспокоясь о состоянии гонки. Например, 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);
}
вы не вернулись из оператора Cache::lock("api-canceling-{$orderId}")->get(function () use ($orderId) { Вы уверены, что ваш код возвращает ответ в случае успеха
@hhsadiq, вы заставили меня понять, что если у меня есть ошибка внутри закрытия замка, и он выходит из строя, он остается заблокированным навсегда. Есть ли у вас какая-либо стратегия, чтобы справиться с этим лучше?
так что нам все объяснили очень хорошо, и он справляется с ситуацией, чтобы избежать вечной блокировки. stackoverflow.com/questions/61430799/… Мы внедрили его рекомендованный подход, и он будет запущен сегодня. Посмотрим, как это работает на производстве :-)
у вас должна быть какая-то глобальная (т.е. БД) запись, в которой хранится статус возврата. Поэтому, если возврат уже возвращен, ничего не делайте по второму запросу.