node.js - Firebase数据库如何防止并发读取?

标签 node.js firebase firebase-realtime-database google-cloud-functions

用例:

当用户位于特定位置(例如 LocationA)时,用户将获得门的密码(例如 door2,密码 222)。之后,云函数将从文档中删除门,并将其添加到占用文档中。

初始数据库:

"LocationA" : {
  "empty" : {
    "door2" : {
      "password" : "222"
    },
    "door3" : {
      "password" : "333"
    }
  },
  "occupied" : {
    "door1" : {
      "password" : "111"
    }
  }
}

用户获得空门密码后:

"LocationA" : {
  "empty" : {
    "door3" : {
      "password" : "333"
    }
  },
  "occupied" : {
    "door1" : {
      "password" : "111"
    },
    "door2" : {
      "password" : "222"
    }
  }
}

问题:

如果有 2 个用户同时获取 door2 密码怎么办?这种情况会发生吗?

我想让用户 1 分别获取 door2,用户 2 分别获取 door3

这是我用来开门的代码:

// Read Lockers QR User(CRUD)
exports.getQRCode = functions.https.onRequest((req, res) => {
    admin.database().ref('lockers/' + 'LocationA/' + 'empty').limitToFirst(1).once("value",snap=> {
        console.log('QR Code for door:',snap.val());
        var qrCodesForDoor = snap.val();
        res.send(qrCodesForDoor); 
    });
});

更新了 Grimthorr 答案的基础

exports.getQRCode = functions.https.onRequest((req, res) => {


admin.database().ref('lockers/LocationA/empty').limitToFirst(1).once("value", snap=> {
  // Get the name of the first available door and use a transaction to ensure it is not occupied
  console.log('QR Code for door:',snap.val());
  var door = Object.keys(snap.val())[0];
  console.log('door:',door);

  // var door = snap.key();
  var occupiedRef = admin.database().ref('lockers/LocationA/occupied/'+door);
  occupiedRef.transaction(currentData=> {
      if (currentData === null) {
          console.log("Door does not already exist under /occupied, so we can use this one.");
          return snap.child(door).val(); // Save the chosen door to /occupied
      } else {
          console.log('The door already exists under /occupied.');
          return nil; // Abort the transaction by returning nothing
      }
  }, (error, committed, snapshot) => {
      console.log('snap.val():',snap.val());
      if (error) {
          console.log('Transaction failed abnormally!', error);
          res.send("Unknown error."); // This handles any abormal error
      } else if (!committed) {
          console.log('We aborted the transaction (because the door is already occupied).');
          res.redirect(req.originalUrl); // Refresh the page so that the request is retried
      } else {
          // The door is not occupied, so can be given to this user
          admin.database().ref('lockers/LocationA/empty/'+door).remove(); // Delete the door from /empty
          console.log('QR Code for door:',snapshot.val());
          var qrCodesForDoor = snapshot.val();
          res.send(qrCodesForDoor); // Send the chosen door as the response
      }
  });
  });
});

最佳答案

您所描述的内容听起来类似于 race condition :

the behavior of [...] software where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended.

在使用实时数据库时,尤其是在云函数中使用时,这似乎不太可能发生,但这并非完全不可能。

Firebase SDK 提供 transaction operations可用于避免并发修改。对于您的场景,使用 Node.js 中的 Admin SDK,您可以执行如下操作:

// Read Lockers QR User(CRUD)
exports.getQRCode = functions.https.onRequest((req, res) => {
    admin.database().ref('lockers/LocationA/empty').limitToFirst(1).once("value", (snap) => {
        if (!snap.hasChildren()) {
            res.send("No doors available.");
            return;
        }
        // Get the name of the first available door and use a transaction to ensure it is not occupied
        var door = Object.keys(snap.val())[0]; // The limitToFirst always returns a list (even with 1 result), so this will select the first result
        var occupiedRef = admin.database().ref('lockers/LocationA/occupied/'+door);
        occupiedRef.transaction((currentData) => {
            if (currentData === null) {
                console.log("Door does not already exist under /occupied, so we can use this one.");
                return snap.val(); // Save the chosen door to /occupied
            } else {
                console.log('The door already exists under /occupied.');
                return; // Abort the transaction by returning nothing
            }
        }, (error, committed, snapshot) => {
            if (error) {
                console.log('Transaction failed abnormally!', error);
                res.send("Unknown error."); // This handles any abormal error
            } else if (!committed) {
                console.log('We aborted the transaction (because the door is already occupied).');
                res.redirect(req.originalUrl); // Refresh the page so that the request is retried
            } else {
                // The door is not occupied, so can be given to this user
                admin.database().ref('lockers/LocationA/empty/'+door).remove(); // Delete the door from /empty
                console.log('QR Code for door:',snapshot.val());
                var qrCodesForDoor = snapshot.val();
                res.send(qrCodesForDoor); // Send the chosen door as the response
            }
        });
    });
});

这使用您现有的代码来获取下一个可用的门,不同之处在于,只有当 /ocpied Node 下尚不存在该门时,它才会选择该门。它通过在选择 /ocpied/door# Node 之前使用事务检查其值来实现此目的,并应用以下逻辑:

  • 如果/ocpied不存在门,我们可以安全地选择这扇门,将其保存到/ocpied中并将其删除/空
  • 如果/占用确实存在门,那么其他人已经抢在我们前面,因此刷新请求页面以再次触发该功能,从而(希望如此) )下次选择不同的门。

关于node.js - Firebase数据库如何防止并发读取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50637113/

相关文章:

javascript - 将 HTML 剥离为允许的标签

javascript - 如何在云函数中调用 firestore getDocument 并使用云函数将其响应发送回客户端?

node.js - 如果使用 knex 不存在,如何忽略列更新?

node.js - 通过用户身份验证防止中间人攻击(Node/Vue/Passport)

javascript - TypeError : _this. props.editAp 不是一个函数

firebase - Firestore : Query documents by property of object

firebase - 在 Flutter 中处理 Firebase 权限不足的错误

java - 如何检查数据库的分支中是否存在子项,以便我可以根据该子项返回值

javascript - 使用 Firebase 单击多个对话时无法禁用监听器

java - 到达时间选择器选择的时间后,如何自动删除我的 firebase 数据库中的整个帖子?