mongoDB 和 nodeJs 无法正确处理并发请求

Posted

技术标签:

【中文标题】mongoDB 和 nodeJs 无法正确处理并发请求【英文标题】:Concurrent request can not handle properly with mongoDB and nodeJs 【发布时间】:2018-11-25 03:17:51 【问题描述】:

我正在开发基于 socket.io room 的项目。我将 socket 与 nodejs 一起使用并在 mongoDB 中管理房间数据。 这是我的代码,只有两个玩家可以加入一个房间,然后我将 IsGameOn 标志设置为 false 为 true。 当我一一向服务器发送请求时,此代码工作正常。 当一次有多个请求时会出现问题。问题是超过 2 个玩家加入房间(房间玩家的数据存储在一个玩家数组中)。

我还上传了数据库的图像。因此,您可以看到数据库中实际发生的情况。

const joinRoom = async (sData, callback) => 

if(sData.iPlayerId && sData.eRoomCount)

    try 

        let body = _.pick(sData, ['eRoomCount', 'iPlayerId']);

        console.log(body);

        await roomsModel.aggregate([
            
                $match: 
                    eRoomCount: body.eRoomCount,
                    IsGameOn:  $eq: false 
                
            ,
             $unwind: "$aPlayers" ,
            
                $group: 
                    _id: "$_id",
                    eRoomCount:  $first: "$eRoomCount" ,
                    aPlayers:  $push: "$aPlayers" ,
                    size:  $sum: 1 
                
            ,
            
                $match: 
                    size:  '$lt': body.eRoomCount 
                
            ,
             $sort:  size: -1  
        ]).exec((error, data) => 
            if (data.length < 1) 

                let params = 
                    eRoomCount: body.eRoomCount,
                    aPlayers: [
                        iPlayerId: body.iPlayerId
                    ]
                
                let newRoom = new roomsModel(params);
                console.log(JSON.stringify(newRoom));
                newRoom.save().then((room) => 
                    console.log("succ", room);
                    callback(null,room);
                ).catch((e) => 
                    callback(e,null);
                );
             else 
                roomsModel.findOne( _id: data[0]._id , (error, room) => 

                    if (error) 
                        callback(error,null);
                    

                    if (!room) 
                        console.log("No room found");
                        callback("No room found",null);
                    

                    room.aPlayers.push( iPlayerId: body.iPlayerId );
                    if (room.aPlayers.length === room.eRoomCount) 
                        room.IsGameOn = true;
                    

                    room.save().then((room) => 
                        callback(null,room);
                    ).catch((e) => 
                        callback(e,null);
                    );

                )
            
        );

     catch (e) 
        console.log(`Error :: $e`);
        let err = `Error :: $e`;
        callback(e,null);
    
    

当请求一个接一个时会发生这种情况。

当一次有多个请求时会发生这种情况。

【问题讨论】:

如何访问 MongoDB?你在用猫鼬吗? 是的,我正在使用猫鼬。 旁注,像这样混合promise、callbacks和await/async,势必会导致后期维护混乱。 【参考方案1】:

正确的方法是使用猫鼬的findOneAndUpdate 而不是findOne。操作findOneAndUpdate 是原子的。如果你做正确的查询,你可以让你的代码线程安全。

// This makes sure, that only rooms with one or no player gets selected.
query = 
    // Like before
    _id: data[0]._id,
    // This is a bit inelegant and can be improved (but would work fast)
    $or: 
         aPlayers:  $size: 0  ,
         aPlayers:  $size: 1  
    


// $addToSet only adds a value to a set if it not present.
// This prevents a user playing vs. him/herself
update =  $addToSet:  aPlayers:  iPlayerId: body.iPlayerId   

// This returns the updated document, not the old one
options =  new: true 

// Execute the query
// You can pass in a callback function
db.rooms.findOneAndUpdate(query, update, options, callback)

【讨论】:

【参考方案2】:

对于这个问题,您可以使用信号量模块,在您的情况下,节点服务器在单线程环境中访问并发请求,但操作系统执行多任务工作。信号量模块将帮助您摆脱这个地狱。

npm i semaphore            (安装模块) const sem = require('信号量')(1); (此处要求并指定并发请求限制                    其1) sem.take(函数()); (将您的函数包装在其中)

sem.leave(); (处理完成后调用它,然后下一个请求                   将访问该函数)

const joinRoom = (sData, callback) => 

  sem.take(function () 
    if (sData.iPlayerId && sData.eRoomCount) 

      try 

        let body = _.pick(sData, ['eRoomCount', 'iPlayerId']);
        roomsModel.aggregate([
          
            $match: 
              eRoomCount: body.eRoomCount,
              IsGameOn:  $eq: false 
            
          ,
           $unwind: "$aPlayers" ,
          
            $group: 
              _id: "$_id",
              eRoomCount:  $first: "$eRoomCount" ,
              aPlayers:  $push: "$aPlayers" ,
              size:  $sum: 1 
            
          ,
          
            $match: 
                  size:  '$lt': body.eRoomCount 
                
              ,
               $sort:  size: -1  
            ]).exec((error, data) => 
              if (data.length < 1) 

                let params = 
                  eRoomCount: body.eRoomCount,
                  aPlayers: [
                    iPlayerId: body.iPlayerId
                  ]
                
                let newRoom = new roomsModel(params);
                console.log(JSON.stringify(newRoom));
                newRoom.save().then((room) => 
                  console.log("succ", room);
                  sem.leave();
                  callback(null, room);
                ).catch((e) => 
                  sem.leave();
                  callback(e, null);
                );
               else 
                roomsModel.findOne( _id: data[0]._id , (error, room) => 
                  if (error) 
                    sem.leave();
                    callback(error, null);
                  
                  if (!room) 
                    console.log("No room found");
                    sem.leave();
                    callback("No room found", null);
                  
                  room.aPlayers.push( iPlayerId: body.iPlayerId );
                  if (room.aPlayers.length === room.eRoomCount) 
                    room.IsGameOn = true;
                  
                  room.save().then((room) => 
                    sem.leave();
                    callback(null, room);
                  ).catch((e) => 
                    sem.leave();
                    callback(e, null);
                  );
                );
              
            );
           catch (e) 
            console.log(`Error :: $e`);
            let err = `Error :: $e`;
            callback(e, null);
          
        
      );
    

【讨论】:

这增加了很多样板代码,另一个依赖并降低了应用程序的性能。最好在数据库层面保证数据库的一致性,这可以通过正确的MongoDB函数实现。 但有时我们必须对选定的数据执行多项任务,此时它可能很有用。 可能是这样。关于问题,情况并非如此。在其他未命名的假设情况下,如果这种解决方案是最好的方法,仍然值得怀疑。

以上是关于mongoDB 和 nodeJs 无法正确处理并发请求的主要内容,如果未能解决你的问题,请参考以下文章

使用mongodb在nodejs中使用锁定/事务读取和插入文档

使用mongodb在nodejs中使用锁定/事务读取和插入文档

mongodb 连接如何处理 NodeJS express 服务器中的并发请求?

MongoDB 读/写锁

nodejs +mssql效率低怎么处理

卡在 NodeJS mongoDB find() 查询上