Google App Engine(PHP) - 信号量或替代方案,以避免网上银行的竞争条件

时间:2017-05-17 05:19:33

标签: php google-app-engine semaphore 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信号量扩展程序。

1 个答案:

答案 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);