2016-06-03 5 views
2

Ich versuche, eine wirklich einfache Shopping-App zu bauen, wo Benutzer Artikel mit virtueller Währung kaufen können.Sperren in Mongo und Nodejs

Ich benutze mehrere NodeJS-Prozesse dafür, also habe ich Angst vor dem asynchronen Teil der Dinge. Diese

ist, was ich tue:

app.post('/buy', function(req, res){ 

    User.findOne({_id: req.userId}, function(err, user){ 

     if(user.balance >= ITEM_PRICE){ 

      User.decrementBalance({_id: req.userId}, function(err){ 

       //Do transaction, give item to user, etc 

      }); 

     } else { 
      //Not enough money 
     } 
    }); 
}); 

Das Problem bei diesem Ansatz ist, dass Benutzer mehr/kaufen Anforderungen in einem sehr kurzen Zeitrahmen einreichen können. Dies könnte zu einer Wettlaufsituation führen, bei der die zweite Anfrage das Kontostand des Benutzers überprüft hat, bevor der erste es dekrementieren kann. Dies führt dazu, dass der Benutzer einen negativen Wert hat und viel mehr Gegenstände herausnimmt, als ihm sein Gleichgewicht erlaubte.

Gibt es eine Möglichkeit, dies zu lösen? Ich denke daran, ein User.update() zu machen und zu überprüfen, ob der Benutzer geändert wurde oder nicht. Könnte das funktionieren?

Antwort

4

Sie Model.findOneAndUpdate() dafür verwenden können, die die Abfrage und die Einstellung des Gleichgewichts in einer atomaren Operation kombinieren:

User.findOneAndUpdate({ 
    _id  : req.userId, 
    balance : { $gte : ITEM_PRICE } 
}, { 
    $inc : { balance : -ITEM_PRICE } // there is no $dec 
}, { 
    new : true 
}, function(err, user) { 
    ... 
}); 

Wenn die Bedingungen der Abfrage fehlschlägt (entweder es gibt keinen Benutzer mit, dass ID, oder sie haben nicht genug Balance), user wird null (oder undefined, nicht sicher sein). Da es so aussieht, als ob Sie nur mit angemeldeten Benutzern arbeiten, wird die ID wahrscheinlich immer gültig sein. Wenn also user nicht definiert ist, bedeutet dies, dass ihr Kontostand nicht hoch genug war.

Aufgrund der new : true, wenn ein Benutzerdokument zurückgegeben wird, spiegelt es die neue Balance wider (standardmäßig würde es das alte Dokument zurückgeben).

EDIT: einige weitere Klarstellung: Sie richtig sind in der Beurteilung, daß es eine Race-Bedingung ist .findOne zwischen der Ausführung und die Ausgabe ein Update (das ist, was User.decrementBalance() tun werden).

jedoch findOneAndUpdate ist etwas Besonderes, dass es einen bestimmten MongoDB Befehl verwenden (findAndModify), der garantiert atomar sein, was bedeutet, dass sowohl die Entdeckung und das Update ohne die Möglichkeit einer anderen Operation durchgeführt werden, zwischen diesen Schritten stören .

Ein Auszug der Dokumentation:

Wenn ein einzelnes Dokument modifiziert, sowohl findAndModifyupdate() und die Methode atomar das Dokument zu aktualisieren.

+0

So verarbeitet Mongo Lese-/Schreibvorgänge einzeln? – StevenDaGee

+1

@StevenDaGee Ich aktualisierte meine Antwort mit einigen weiteren Informationen. – robertklep

Verwandte Themen