2016-04-21 52 views
1

我有一個模型叫其具有如下架構如何保證兩個用戶可以原子確認交易已經發生MongoDB中

var transactionSchema = new mongoose.Schema({ 
    amount: Number, 
    status: String, 
    _recipient: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 
    _sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 
}); 

我想無論是發送者和本次交易的收件人能夠「一交易確認交易發生了。 status作爲「初始」開始。因此,當只有發件人確認了交易(但收件人尚未),我想將status更新爲「senderConfirmed」或其他內容,並且當收件人已確認(但發件人尚未)時,我想將狀態更新爲「recipientConfirmed」。當他們有這兩個證實它,我想更新狀態爲「完成」。

問題是,我怎麼知道什麼時候以避免競爭條件的方式將其更新爲「完成」?如果發送方和接收方都同時確認交易,那麼兩個線程都會認爲狀態爲「初始」,並將其更新爲「senderConfirmed」或「recipientConfirmed」,實際上它應該轉到「完成」 。

我讀過關於MongoDB的兩階段提交方法here但這並不完全符合我的需要,因爲我不知道(在另一個線程當前正在修改事務的情況下)想要阻止第二個線程執行其更新 - 我只是想要它等待,直到第一個線程完成更新之前完成,然後使其更新的內容取決於事務的最新狀態。

回答

2

底線是你需要「兩個」更新語句分別爲每個發件人和收件人做這件事。所以基本上一個人會嘗試設置完成「部分」狀態,另一個只會將「初始」狀態匹配設置爲「部分」狀態。

批量操作是實現多個語句的最佳方式,因此您應該通過訪問底層驅動程序方法來使用這些操作。現代API版本具有.bulkWrite()方法,如果服務器版本不支持「批量」協議,則該方法會很好地降級,並且只會退化爲發出單獨的更新。

// sender confirmation 
Transaction.collection.bulkWrite(
    [ 
     { "updateOne": { 
      "filter": { 
       "_id": docId, 
       "_sender": senderId, 
       "status": "recipientConfirmed" 
      }, 
      "update": { 
       "$set": { "status": "complete" } 
      } 
     }}, 
     { "updateOne": { 
      "filter": { 
       "_id": docId, 
       "_sender": senderId, 
       "status": "initial" 
      }, 
      "update": { 
       "$set": { "status": "senderConfirmed" } 
      } 
     }} 
    ], 
    { "ordered": false }, 
    function(err,result) { 
     // result will confirm only 1 update at most succeeded 
    } 
); 

當然這同樣適用於_recipient,除了不同的狀態檢查或更改。您可以在_sender_recipient上交替發出$or條件,並且具有通用的「部分」狀態,而不是編碼不同的更新條件,但適用相同的基本「兩次更新」過程。

當然你也可以使用常規方法並以另一種方式向服務器發佈兩個更新,甚至可能並行,因爲條件仍然是「原子」,但這也是{ "ordered": false }選項的原因,因爲他們沒有確定的順序,需要在這裏受到尊重。

批量操作雖然比單獨的調用要好,因爲發送和返回只有一個請求和響應,而不是每個「兩個」,所以使用批量操作的開銷要少得多。

但這是一般的方法。在另一方也發佈確認之前,沒有任何一項聲明可能會將「狀態」置於「僵局」或標記爲「完整」。

在第一次嘗試更新和第二次更新之間,狀態從「初始」改變爲「可能」和非常渺茫,這將導致沒有更新。在這種情況下,您可以「重試」它在後續嘗試中「應該」更新的操作。

儘管這應該只需要「一次」重試。而且很少。


注意:應謹慎使用上的貓鼬模型.collection訪問時服用。所有的常規模型方法都內置了邏輯來「確保」與數據庫的連接在他們做任何事之前實際存在,並且實際上是「排隊」操作直到存在連接。

它通常是很好的做法來包裝你的應用程序啓動時的事件處理程序,以確保數據庫連接:

mongoose.on("open",function() { 
    // App startup and init here 
}) 

所以使用這種情況下,"on""once"事件。

通常,在事件被觸發後或者在應用程序中已經調用任何「常規」模型方法後,總是存在連接。

可能貓鼬會在未來版本中直接在模型方法中包含像.bulkWrite()這樣的方法。但目前它沒有,所以.collection訪問器有必要從核心驅動程序中獲取底層的Collection對象。

0

更新:我在澄清我的答案的基礎上,我原來的迴應沒有提供答案的評論。

另一種方法是跟蹤的狀態作爲兩個單獨的屬性:

senderConfirmed: true/false, 
recipientConfirmed: true/false, 

當發送者確認您只需更新senderConfirmed領域。收件人確認您更新recipientConfirmed字段時。他們無法相互覆蓋。

要確定交易是否完成,您只需查詢{senderConfirmed:true,recipientConfirmed:true}

顯然這是對文檔模式的改變,所以它可能並不理想。

原來的答案

是一個改變你的模式可能嗎?如果你有兩個屬性 - senderStatusrecipientStatus?發件人只會更新senderStatus,收件人只會更新recipientStatus。然後他們不能改寫每個人的變化。

我想,您仍然需要其他方式將其標記爲完整。你可以給我們一個cron工作或者其他什麼...