MongoDB的Two Phase Commits

Mr. Whatever
4 min readAug 27, 2015

--

mongodb沒有transaction的功能,transaction簡單說就是所有指定的操作都要完成才算完成,中途有失敗的話會返回(rollback)原始狀態。

而工作內容中,有一樣儲值功能是需要用到transaction的,避免金錢上的糾紛。但我又不想多加一個MySQL來實現這個功能,所以就找到MongoDB為自己的缺失所提供的解決方法

說是儲值,但真正的金錢來往是Google跟Apple在做,這功能也只是新增交易記錄,並增加使用者代幣。所以提醒一下,真正重要或複雜的transaction還是要使用適合的資料庫系統來做會比較妥當。

根據文章實現功 能後,整理了流程,以下的transaction是使用者的交易記錄,而非前述的DB transaction了,不要搞混。

1. 驗證交易有效後,beginTransaction。這裡與原文不同的用意在,pending中失敗的交易項,client重發要求即可往下進行,server無需Rollback。(Google及Apple已付款完成,本就要重試到成功為止)

db.transaction.findOne({_id: tid}, function(err, result) {
if(!result)
db.transaction.insert({_id: tid, ..., state: 'pending'});
if(result.state == 'pending')
callback(null, result);
else
callback(new Error());
})

2. purchaseGems,檢查是否為進行中的交易,避免重覆,多給代幣

db.PlayerData.findAndModify(
{_id: acc, pendingTransaction: {$ne: tid}},
{$inc: {gems: amount}, $push: {pendningTransaction: tid}}
)

3. commitTransaction,更新該筆交易狀態為交付,檢查目前狀態可避免因多傳的request造成狀態覆蓋而錯誤

db.transaction.findAndModify(
{_id: tid, state: 'pending'},
{$set: {state: 'applied'}}
)

4. removeTransactionPending,移除玩家身上的pending交易記錄

db.PlayerData.findAndModify(
{_id: acc, pendingTransaction: tid},
{$pull: {pendingTransaction: tid}
)

5. doneTransaction,在移除玩家身上的pending記錄後,才算全部完成,將交易狀態改為done。

db.transaction.findAndModify(
{_id: tid, state: 'applied'},
{$set: {state: 'done'}
)

我的例子並不需要Rollback的操作,所以相關的內容就再自行看原文了。

在步驟2.以前的DB發生異常,會回傳操作失敗,就等待玩家重發要求,便可繼續進行。

步驟2.成功後,就回傳client,後續的旗標變更,就丟給自己寫的Manager慢慢處理,如此才不會拖慢回應時間。

若再遇到重覆發送要求,也會因為findAndModify的條件而擋掉多餘的要求。

雖然沒有方便的transaction可以使用,但對於mongodb想出的這套解法也覺得玄妙,步驟繁多,但的確沒有破綻,厲害厲害。

--

--