我正在开发网上银行,在 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 :(得分: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);