锁定Mongo和Nodejs

Ste*_*Gee 3 mongoose mongodb node.js express

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

我为此使用了多个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
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

这种方法的问题在于,用户可以在很短的时间内提交多个购买请求。这可能会导致出现争用情况,在这种情况下,第二个请求会在第一个请求减少余额之前检查用户的余额。这导致用户具有负值,并且拿出比其余额所允许的更多的物品。

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

rob*_*lep 6

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

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

如果查询条件失败(没有用户使用该ID,或者他们没有足够的余额),user将为null(或undefined,不确定)。由于您似乎只在与登录用户打交道,因此该ID可能始终有效,因此,如果user未定义ID,则意味着他们的余额不够高。

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

编辑:需要更多说明:在执行.findOne和发布更新之间(这是User.decrementBalance()将要执行的操作)之间存在竞争条件的评估是正确的。

但是,findOneAndUpdate它的特殊之处在于它将使用特定的MongoDB命令(findAndModify),该命令保证是原子的,这意味着将执行查找和更新操作,而不会在这些步骤之间干扰其他操作。

文档摘录:

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