Google App Engine (PHP) — Семафоры или альтернативы, чтобы избежать Race Condition для интернет-банкинга

Я разрабатываю Интернет-банкинг, работает на Google App Engine и разработан в PHP.

Состояние гонки — большая проблема.
У пользователей никогда не может быть меньше 0,00 долларов (я знаю, что у меня есть другие проблемы, о которых стоит беспокоиться, но давайте сосредоточимся только на этом прямо сейчас).

Псевдо-код:

<?php

$user_id =         $_GET['user_id'];
$withdraw_amount = $_GET['withdraw_amount'];

if(getUserBalance($user_id) - $withdraw_amount >= 0){
setUserBalance($user_id, getUserBalance($user_id) - $withdraw_amount);
sendMoneyToUser($user_id, $withdraw_amount);
echo 'Success';
}
else{
echo 'Insufficient funds';
}

?>

База данных Google Cloud SQL:

USER_ID     BALANCE
1         10.00
2         20.00

* Приведенная выше база данных является очень упрощенной версией фактической базы данных. Таблицы / строки блокировки SQL не будут работать для меня в реальном мире. Но если у вас есть другое решение, использующее SQL вместо PHP, я хочу знать об этом.

Случай гонки:

GAE instance #1                 GAE instance #2    (SAME TIME)

user_id = '1'                   user_id = '1'
withdraw_amount = 10.00         withdraw_amount = 10.00

balance = getUserBalance('1')   balance = getUserBalance('1')
//balance = 10.00               //balance = 10.00

10.00 - 10.00 >= 0  ->  (true)  10.00 - 10.00 >= 0  ->  (true)

setUserBalance ('1',  0.00)     setUserBalance ('1',  0.00)
sendMoneyToUser('1', 10.00)     sendMoneyToUser('1', 10.00)

User gets $10.00                User gets $10.00

The user now has $20.00 in his hands, but he had just $10.00 in the bank!!!

Как я могу предотвратить условия гонки?

Помните, что код работает на Google App Engine и у меня нет доступа к Семафоры PHP расширение.

0

Решение

Одним из способов будет использование библиотеки мьютексов, которая использует что-то вроде redis в качестве бэкэнда, чтобы вы могли без проблем вызывать его с разных машин.

Получите блокировку, специфичную для пользователя, до начала операции, выполните свою работу и затем снимите блокировку. Пока вы выполняете работу, последующие вызовы для выполнения операции не будут выполнены, поскольку они не могут получить блокировку.

В псевдокоде это будет так просто:

$lockName = "some string that contains the user/customer ID in it to be unique per customer";
if ( ! $mutex->acquireLock($lockName, 60) ) {
throw new Exception ("An operation is already running, please wait for it to finish and try again!");
}

// do the transaction data here, substract the amount, etc.

// once done, release the lock:
$mutex->releaseLock($lockName);
0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]