Locks in Mongo and Nodejs



我正在尝试构建一个非常简单的购物应用程序,用户可以在其中使用虚拟货币购买商品。

为此,我使用了多个NodeJS进程,所以我害怕异步部分。

这就是我所做的:

app.post('/buy', function(req, res){
User.findOne({_id: req.userId}, function(err, user){
if(user.balance >= ITEM_PRICE){
User.decrementBalance({_id: req.userId}, function(err){
//Do transaction, give item to user, etc
});
} else {
//Not enough money
}
});
});

这种方法的问题在于,用户可以在很短的时间内提交多个/购买请求。这可能会导致一种竞争条件,即第二个请求在第一个请求可以递减之前检查用户的余额。这会导致用户的余额为负值,并取出比余额允许的多得多的项目。

有办法解决这个问题吗?我正在考虑做一个User.update()并验证用户是否被修改。这行得通吗?

您可以使用Model.findOneAndUpdate(),它将在一个原子操作中组合查询余额调整:

User.findOneAndUpdate({
_id     : req.userId,
balance : { $gte : ITEM_PRICE }
}, {
$inc : { balance : -ITEM_PRICE } // there is no $dec
}, {
new : true
}, function(err, user) {
...
});

如果查询条件失败(或者没有具有该id的用户,或者他们没有足够的余额),则user将是null(或者undefined,不确定)。由于看起来您只处理登录用户,因此id可能始终有效,因此如果未定义user,则意味着他们的余额不够高。

由于new : true,如果返回用户文档,它将反映余额(默认情况下,它将返回旧文档)。

EDIT:更多澄清:您正确地评估了执行.findOne和发布更新之间存在竞争条件(这就是User.decrementBalance()将要做的)。

然而,findOneAndUpdate是特殊的,因为它将使用一个特定的MongoDB命令(findAndModify),该命令保证是原子的,这意味着查找和更新都将在没有其他操作干扰这些步骤的情况下执行。

文件摘录:

修改单个文档时,findAndModifyupdate()方法都会自动更新文档。

最新更新