目錄:
- 基礎概念
- 分布式事務理論
- 分布式事務解決方案之2pc
- 分布式事務解決方案之TCC(本章)
- 分布式事務解決方案之可靠消息最終一緻性
- 分布式事務解決方案之最大努力通知
- 分布式事務綜合案例分析
4.分布式事務解決方案之TCC
4.1.什麼是TCC事務
TCC是Try、Confifirm、Cancel三個詞語的縮寫,TCC要求每個分支事務實作三個操作:預處理Try、确認Confifirm、撤銷Cancel。Try操作做業務檢查及資源預留,Confifirm做業務确認操作,Cancel實作一個與Try相反的操作即復原操作。TM首先發起所有的分支事務的try操作,任何一個分支事務的try操作執行失敗,TM将會發起所有分支事務的Cancel操作,若try操作全部成功,TM将會發起所有分支事務的Confifirm操作,其中Confifirm/Cancel操作若執行失敗,TM會進行重試。
分支事務失敗的情況:
TCC分為三個階段:
- Try 階段是做業務檢查(一緻性)及資源預留(隔離),此階段僅是一個初步操作,它和後續的Confifirm 一起才能真正構成一個完整的業務邏輯。
- Confifirm 階段是做确認送出,Try階段所有分支事務執行成功後開始執行 Confifirm。通常情況下,采用TCC則認為 Confifirm階段是不會出錯的。即:隻要Try成功,Confifirm一定成功。若Confifirm階段真的出錯了,需引入重試機制或人工處理。
- Cancel 階段是在業務執行錯誤需要復原的狀态下執行分支事務的業務取消,預留資源釋放。通常情況下,采用TCC則認為Cancel階段也是一定成功的。若Cancel階段真的出錯了,需引入重試機制或人工處理。
TM事務管理器,TM事務管理器可以實作為獨立的服務,也可以讓全局事務發起方充當TM的角色,TM獨立出來是為了成為公用元件,是為了考慮系統結構和軟體複用。
TM在發起全局事務時生成全局事務記錄,全局事務ID貫穿整個分布式事務調用鍊條,用來記錄事務上下文,追蹤和記錄狀态,由于Confifirm 和cancel失敗需進行重試,是以需要實作為幂等,幂等性是指同一個操作無論請求多少次,其結果都相同。
4.2.TCC 解決方案
目前市面上的TCC架構衆多比如下面這幾種: (以下資料采集日為2019年07月11日)
架構名稱 | Gitbub位址 | star數量 |
---|---|---|
tcc-transaction | github.com/changmingxi… | 3850 |
Hmily | github.com/yu199195/hm… | 2407 |
ByteTCC | github.com/liuyangming… | 1947 |
EasyTransaction | github.com/QNJR-GROUP/… | 1690 |
上一節所講的Seata也支援TCC,但Seata的TCC模式對Spring Cloud并沒有提供支援。我們的目标是了解TCC的原理以及事務協調運作的過程,是以更請傾向于輕量級易于了解的架構,是以最終确定了Hmily。
Hmily是一個高性能分布式事務TCC開源架構。基于Java語言來開發(JDK1.8),支援Dubbo,Spring Cloud等RPC架構進行分布式事務。它目前支援以下特性:
- 支援嵌套事務(Nested transaction support).
- 采用disruptor架構進行事務日志的異步讀寫,與RPC架構的性能毫無差别。
- 支援SpringBoot-starter 項目啟動,使用簡單。
- RPC架構支援 : dubbo,motan,springcloud。
- 本地事務存儲支援 : redis,mongodb,zookeeper,fifile,mysql。
- 事務日志序列化支援 :java,hessian,kryo,protostuffff。
- 采用Aspect AOP 切面思想與Spring無縫內建,天然支援叢集。
- RPC事務恢複,逾時異常恢複等。
Hmily利用AOP對參與分布式事務的本地方法與遠端方法進行攔截處理,通過多方攔截,事務參與者能透明的調用到另一方的Try、Confifirm、Cancel方法;傳遞事務上下文;并記錄事務日志,酌情進行補償,重試等。
Hmily不需要事務協調服務,但需要提供一個資料(mysql/mongodb/zookeeper/redis/fifile)來進行日志存儲。
Hmily實作的TCC服務與普通的服務一樣,隻需要暴露一個接口,也就是它的Try業務。Confifirm/Cancel業務邏輯,隻是因為全局事務送出/復原的需要才提供的,是以Confifirm/Cancel業務隻需要被Hmily TCC事務架構發現即可,不需要被調用它的其他業務服務所感覺。
TCC需要注意三種異常處理分别是空復原、幂等、懸挂:
空復原:
在沒有調用 TCC 資源 Try 方法的情況下,調用了二階段的 Cancel 方法,Cancel 方法需要識别出這是一個空復原,然後直接傳回成功。
出現原因是當一個分支事務所在服務當機或網絡異常,分支事務調用記錄為失敗,這個時候其實是沒有執行Try階段,當故障恢複後,分布式事務進行復原則會調用二階段的Cancel方法,進而形成空復原。
解決思路的關鍵就是要識别出這個空復原。思路很簡單就是需要知道一階段是否執行,如果執行了,那就是正常復原;如果沒執行,那就是空復原。前面已經說過TM在發起全局事務時生成全局事務記錄,全局事務ID貫穿整個分布式事務調用鍊條。再額外增加一張分支事務記錄表,其中有全局事務 ID 和分支事務 ID,第一階段 Try 方法裡會插入一條記錄,表示一階段執行了。Cancel 接口裡讀取該記錄,如果該記錄存在,則正常復原;如果該記錄不存在,則是空復原。
幂等:
通過前面介紹已經了解到,為了保證TCC二階段送出重試機制不會引發資料不一緻,要求 TCC 的二階段 Try、Confifirm 和 Cancel 接口保證幂等,這樣不會重複使用或者釋放資源。如果幂等控制沒有做好,很有可能導緻資料不一緻等嚴重問題。
解決思路在上述“分支事務記錄”中增加執行狀态,每次執行前都查詢該狀态。
懸挂:
懸挂就是對于一個分布式事務,其二階段 Cancel 接口比 Try 接口先執行。
出現原因是在 RPC 調用分支事務try時,先注冊分支事務,再執行RPC調用,如果此時 RPC 調用的網絡發生擁堵,通常 RPC 調用是有逾時時間的,RPC 逾時以後,TM就會通知RM復原該分布式事務,可能復原完成後,RPC 請求才到達參與者真正執行,而一個 Try 方法預留的業務資源,隻有該分布式事務才能使用,該分布式事務第一階段預留的業務資源就再也沒有人能夠處理了,對于這種情況,我們就稱為懸挂,即業務資源預留後沒法繼續處理。
解決思路是如果二階段執行完成,那一階段就不能再繼續執行。在執行一階段事務時判斷在該全局事務下,“分支事務記錄”表中是否已經有二階段事務記錄,如果有則不執行Try。
舉例,場景為 A 轉賬 30 元給 B,A和B賬戶在不同的服務。
方案1:
賬戶A
try:
檢查餘額是否夠30元
扣減30元
confirm:
空
cancel:
增加30元
賬戶B
try:
增加30元
confirm:
空
cancel:
減少30元
方案1說明:
1)賬戶A,這裡的餘額就是所謂的業務資源,按照前面提到的原則,在第一階段需要檢查并預留業務資源,是以,我們在扣錢 TCC 資源的 Try 接口裡先檢查 A 賬戶餘額是否足夠,如果足夠則扣除 30 元。 Confifirm 接口表示正式送出,由于業務資源已經在 Try 接口裡扣除掉了,那麼在第二階段的 Confifirm 接口裡可以什麼都不用做。Cancel接口的執行表示整個事務復原,賬戶A復原則需要把 Try 接口裡扣除掉的 30 元還給賬戶。
2)賬号B,在第一階段 Try 接口裡實作給賬戶B加錢,Cancel 接口的執行表示整個事務復原,賬戶B復原則需要把Try 接口裡加的 30 元再減去。
方案1的問題分析:
- 如果賬戶A的try沒有執行在cancel則就多加了30元。
- 由于try,cancel、confifirm都是由單獨的線程去調用,且會出現重複調用,是以都需要實作幂等。
- 賬号B在try中增加30元,當try執行完成後可能會其它線程給消費了。
- 如果賬戶B的try沒有執行在cancel則就多減了30元。
問題解決:
- 賬戶A的cancel方法需要判斷try方法是否執行,正常執行try後方可執行cancel。
- try,cancel、confifirm方法實作幂等。
- 賬号B在try方法中不允許更新賬戶金額,在confifirm中更新賬戶金額。
- 賬戶B的cancel方法需要判斷try方法是否執行,正常執行try後方可執行cancel。
優化方案:
賬戶A
try:
try幂等校驗
try懸挂處理
檢查餘額是否夠30元
扣減30元
confirm:
空
cancel:
cancel幂等校驗
cancel空復原處理
增加可用餘額30元
賬戶B
try:
空
confirm:
confirm幂等校驗
正式增加30元
cancel:
空
最後
後面的内容,會按章節更新的,可以關注我持續閱讀,覺得不錯可以點個贊支援一下!