假設我在快速服務器上有這個端點:
app.get('/', async (req, res) => {
var foo = await databaseGetFoo();
if (foo == true) {
foo = false;
somethingThatShouldOnlyBeDoneOnce();
await databaseSetFoo(foo);
}
})
如果端點被同時呼叫兩次,我認為這會產生競爭條件嗎?如果是這樣,我怎樣才能防止這種競爭條件的發生?
uj5u.com熱心網友回復:
好的,所以根據評論,我對您在這里想要什么有了更好的了解。
假設somethingThatShouldOnlyBeDoneOnce正在執行一些異步操作(例如寫入資料庫),那么您是正確的,一個用戶(或多個用戶)對該端點進行多次呼叫可能會導致該操作重復發生。
使用您關于允許每個用戶發表一條評論的評論,并假設您在中間件堆疊中的早期有可以通過會話或其他方式唯一標識用戶的中間件,您可以天真地實作這樣的事情,這樣應該可以讓您遠離麻煩(通常披露這是未經測驗的等):
let processingMap = {};
app.get('/', async (req, res, next) => {
if (!processingMap[req.user.userId]) {
// add the user to the processing map
processingMap = {
...processingMap,
[req.user.userId]: true
};
const hasUserAlreadySubmittedComment = await queryDBForCommentByUser(req.user.userId);
if (!hasUserAlreadySubmittedComment) {
// we now know we're the only comment in process
// and the user hasn't previously submitted a comment,
// so submit it now:
await writeCommentToDB();
delete processingMap[req.user.userId];
res.send('Nice, comment submitted');
} else {
delete processingMap[req.user.userId];
const err = new Error('Sorry, only one comment per user');
err.statusCode = 400;
next(err)
}
} else {
delete processingMap[req.user.userId];
const err = new Error('Request already in process for this user');
err.statusCode = 400;
next(err);
}
})
由于插入 processingMap 都是同步的,并且 Node 一次只能做一件事,因此用戶訪問此路由處理程式的第一個請求實際上將為該用戶鎖定,直到我們完成處理后鎖定被移除要求。
但是......這是一個天真的解決方案,它違反了12 因素應用程式的規則。具體來說,規則 6是您的應用程式應該是無狀態行程。我們現在已經在您的應用程式中引入了狀態。
如果你確信你只會永遠運行這個作為一個單一的程序中,你的罰款。但是,當您通過部署多個節點(通過任何方法——PM2、Node 的 process.cluster、Docker、K8s 等)來進行水平擴展時,您就會遇到上述解決方案。節點服務器 1 不知道節點服務器 2 的本地狀態,因此命中多節點應用程式不同實體的多個請求無法共同管理處理映射的狀態。
更強大的解決方案是實作某種佇列系統,可能會利用一個單獨的基礎設施,如 Redis。這樣,您的所有節點都可以使用同一個 Redis 實體來共享狀態,現在您可以擴展到應用程式的許多實體,并且所有這些實體都可以共享資訊。
我真的沒有關于如何構建它的所有細節,無論如何它似乎超出了這個問題的范圍,但希望我已經給了你至少一個解決方案和一些想法更廣的層面。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/334829.html
標籤:javascript 节点.js 表达 网络服务器
