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

时间:2018-06-15 09:35:39

标签: node.js mongodb socket.io

我正在处理我的项目,该项目基于socket.io room。我在socket中使用了nodejs并在mongoDB中管理房间数据。

这是我的代码,只有两个玩家可以加入一个房间然后我将IsGameOn标志为false为真 当我逐个向服务器发送请求时,此代码正常工作 当许多请求一次出现时会出现问题。问题是超过2名玩家加入了房间(房间玩家的数据存储在aPlayers Array中)。

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

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);
    }
    }
}

请求一个接一个地发生这种情况。  This is happens when request comes one by one.

当许多请求一次出现时会发生这种情况。 enter image description here

2 个答案:

答案 0 :(得分:2)

正确的方法是使用mongoose的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)

答案 1 :(得分:2)

对于这个问题,您可以使用信号量模块,在您的情况下,Node Server在单个线程环境中访问并发请求,但OS执行多任务处理作业。信号量模块将帮助你克服这个地狱。

  • npm i semaphore(安装模块)
  • const sem = require('semaphore')(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);
              }
            }
          });
        }