【问题标题】:Concurrent request can not handle properly with mongoDB and nodeJsmongoDB 和 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,势必会导致后期维护混乱。

标签: node.js mongodb socket.io


【解决方案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函数实现。
    • 但有时我们必须对选定的数据执行多项任务,此时它可能很有用。
    • 可能是这样。关于问题,情况并非如此。在其他未命名的假设情况下,如果这种解决方案是最好的方法,仍然值得怀疑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-09
    相关资源
    最近更新 更多