用例:

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

初始数据库:

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


根据格里姆索尔答案更新

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


  daccess-ods.un.org daccess-ods.un.org输出取决于其他不可控制事件的顺序或时间的软件行为。当事件没有按照程序员的预期顺序发生时,它将成为一个错误。


使用实时数据库时,尤其是在Cloud Function中使用时,这似乎是不太可能的情况,但这并不是完全不可能的。

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


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


如果在/occupied下门不存在,我们可以安全地选择此门,将其保存到/occupied并将其从/empty删除。
如果/occupied下的门确实存在,则表明有人殴打了我们,因此刷新了请求页面以再次触发该功能,因此(希望)下次选择其他门。

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

10-12 02:25