用例:
当用户位于特定位置(例如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/