聊一聊如何用C 輕鬆完成乙個TCC分布式事務

2022-09-20 11:24:10 字數 3740 閱讀 4118

銀行跨行轉賬業務是乙個典型分布式事務場景,假設 a 需要跨行轉賬給 b,那麼就涉及兩個銀行的資料,無法通過乙個資料庫的本地事務保證轉賬的 acid ,只能夠通過分布式事務來解決。

在 聊一聊如何用c#輕鬆完成乙個saga分布式事務 中介紹了借助 dtm 用 saga 事務模式解決了上面的銀行跨行轉賬業務。

這一篇我們就來看看如何用 tcc 的事務模式來處理這個問題。

tcc是try、confirm、cancel三個詞語的縮寫,最早是由 pat helland 於 2007 年發表的一篇名為《life beyond distributed transactions:an apostate』s opinion》的**提出。

tcc分為3個階段

對於前面的跨行轉賬業務,最簡單的做法是,在try階段調整餘額,在cancel階段反向調整餘額,confirm階段則空操作。這麼做帶來的問題是,如果a扣款成功,金額轉入b失敗,最後回滾,把a的餘額調整為初始值。在這個過程中如果a發現自己的餘額被扣減了,但是收款方b遲遲沒有收到餘額,那麼會對a造成困擾。

更好的做法是,try階段凍結a轉賬的金額,confirm進行實際的扣款,cancel進行資金解凍,這樣使用者在任何乙個階段,看到的資料都是清晰明了的。

下面我們進行乙個 tcc 事務的具體開發

前置工作

dotnet add package dtmcli --version 0.4.0
注:相比 0.3.0,0.4.0 支援了 4 個新的特性,詳見

先來看一下乙個成功完成的 tcc 時序圖。

可以看到它的流程和 saga 的還是有比較大的區別。

同樣的,上圖的微服務1,對應我們示例的 outapi,也就是轉錢出去的那個服務。

微服務2,對應我們示例的 inapi,也就是轉錢進來的那個服務。

下面我們來編寫兩個服務的try/confirm/cancel的處理。

outapi

】轉出【】try 操作,bb=");

// tx 引數是事務,可和本地事務一起提交回滾

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});】轉出【】confirm操作,bb=");

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});】轉出【】cancel操作,bb=");

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});inapi

】轉入【】try操作,bb=");

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});】轉入【】confirm操作,bb=");

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});】轉入【】cancel操作,bb=");

await task.completedtask;

});return results.ok(transresponse.buildsucceedresponse());

});到此各個子事務的處理已經ok了,在上面的**中,下面這幾行是子事務屏障相關**,只要按照這個方式來呼叫您的業務邏輯,子事務屏障保證重複請求、懸掛、空補償情況出現時,您的業務邏輯不會被呼叫,保證了正常業務的正確進行

var bb = bbfactory.createbranchbarrier(context.request.query);

await bb.call(db, async (tx) =>

);

然後準備開啟 tcc 事務,進行分支呼叫

var cts = new cancellationtokensource();

var gid = await dtmclient.gengid(cts.token);

var res = await tccglobaltransaction.excecute(gid, async (tcc) =>

branch-in-res= ");

}, cts.token);

console.writeline($"case1, tcc 提交結果 = ");

到這裡,乙個完整的 tcc 分布式事務就編寫完成了。

需要注意的地方:

依賴 tccglobaltransaction ,這個是單例的

tcc 的 callbranch 方法就是事務分支的呼叫

搭建好 dtm 的環境後,執行上面的例子,會看到下面的輸出。

成功的示例都是相對比較簡單的。

下面來看乙個 tcc 回滾的例子。

假如銀行將金額準備轉入使用者2時,發現使用者2的賬戶異常,返回失敗,會怎麼樣?我們修改**,模擬這種情況:

在 inapi 加多乙個轉入try失敗的處理介面

】轉入【】try--失敗,bb=");

return results.ok(transresponse.buildfailureresponse());

});再來看一下事務失敗互動的時序圖

這個跟成功的 tcc 差別就在於,當某個子事務返回失敗後,後續就回滾全域性事務,呼叫各個子事務的 cancel 操作,保證全域性事務全部回滾。

再調整一下呼叫方,把轉入 try 操作替換成上面這個返回錯誤的介面。

var cts = new cancellationtokensource();

var gid = await dtmclient.gengid(cts.token);

var res = await tccglobaltransaction.excecute(gid, async (tcc) =>

branch-in-res= ");

}, cts.token);

console.writeline($"case2, tcc 提交結果 = ");

需要注意的是 callbranch 方法在對應的微服務返回失敗後會丟擲異常,進而觸發全域性事務的回滾操作,這個時候 dtm 才會觸發 cancel 的操作。

執行結果如下:

重點看三個地方,

在這篇文章裡,通過 2 個簡單的例子,完整給出了編寫乙個 tcc 事務的過程,涵蓋了正常成功完成,異常回滾的情況。

希望對研究分布式事務的您有所幫助。

本文示例**: dtmtccdemo

參考資料

《一天聊乙個設計模式》 抽象工廠

提供乙個介面,用於建立相關的物件家族。抽象工廠模式建立的是物件家族,也就是很多物件而不是乙個物件,並且這些物件是相關的,也就是說必須一起建立出來。而工廠方法模式只是用於建立乙個物件,這和抽象工廠模式有很大不同。抽象工廠模式用到了工廠方法模式來建立單一物件,abstractfactory 中的 cre...

如何用C 寫乙個類

其實寫乙個類很簡單,但是要寫乙個符合各方面要求的類,卻不是一件容易的事情。下面總結一下需要考慮的一些問題,先把暫時想到的方面記下來,以後想到新的在增加吧。類的生死和初始化 1.copy ctor和copy assignment是不是需要private 單例常用 或者是不是要用private繼承來阻止...

如何用c 製作乙個程式

程式設計思想 1.製作合適的窗體.2.將窗體變成圓形.3.設定好圓形窗體的初始位置 大小等資料 4.製作第一輪小球滾動 5.考慮多種情況.製作流程 1.this formborderstyle formborderstyle.none 視窗無邊框 this size newsize 50 50 窗體...