那些用Go實現(xiàn)的分布式事務框架之二
本文轉載自微信公眾號「RememberGo」,作者吳親庫里。轉載本文請聯(lián)系RememberGo公眾號。
開篇
上一篇那些用Go實現(xiàn)的分布式事務框架我們主要介紹的是seata-golang。一個對標seata的go語言實現(xiàn),當然版本還是落后Java版很多的。
這次我們來介紹一下另一個go實現(xiàn)的分布式事務:dtm。
首先來看下dtm整體架構圖(來源官網)。
再來看之前的seata架構圖。
從架構上來看,大差不差。
seata中的TC對標dam的TM。
RM兩邊意思一致。
seata中的TM對標dtm事務SDK。作用都是一樣:第一階段開啟一個全局事務,執(zhí)行各RM分支事務,第二階段根據(jù)RM第一階段執(zhí)行結果,決定調用TC(seata)|TM(dtm) commit或者rollback。
架構上,個人感覺只是因為模塊名稱以及圖畫不一樣的差別,當然在實現(xiàn)細節(jié)上還是有很大差別的。
我們先簡單介紹下DTM各個模塊。
TM
TM 層在代碼中是沒有具體的主體結構的,開始都是函數(shù)之前的調用。
啟動TM實際上開啟了兩個服務,http以及grpc這兩個服務。
http路由,
gRPC接口,
即然提供了兩個服務入口,那理所當然有公共處理核心業(yè)務的部分。
TM對數(shù)據(jù)的存儲管理并不是依賴于接口,而是依賴于common.DB 結構。根據(jù)配置文件中DB.driver 的值決定底層數(shù)據(jù)庫是mysql還是postgres兩種。
再看這個DB結構,所以本質上無論底層是哪種數(shù)據(jù)庫,都是直接依賴gorm來對數(shù)據(jù)進行操作的。
接著,看下TM是如何通知各個RM進行commit或者rollback的?
舉一個TCC模式的例子。
TCC的兩個階段。
- 階段一: try。嘗試執(zhí)行,調用各RM自定義的try行為,預留必要的業(yè)務資源。
- 階段二:Confirm(階段一所有參與本次事務的try行為都成功)。調用各分支事務的Confirm方法,真正執(zhí)行業(yè)務,并且只使用try階段預留的資源。
- 階段二:Cancel(階段一任一參與本次事務的try行為失敗)。調用各分支事務的Cancel方法,釋放一階段try所預留的資源。
從上面我們可以得知,TCC模式下,TM在第二階段要么通知各分支事務Confirm要么Cancel。
在注冊各RM事務分支到TM的時候,最終TM會為每一個分布式事務的參與者(RM)生成兩條分支信息。
就像這樣,
對,就是把對應的RM資源操作地址直接存入。
當TM接收到commit或者rollback命令,在處理完自身邏輯(一般就是修改Gloable狀態(tài)),就需要開始處理每一個注冊進來的分支事務了,說白了就是需要調用各個分支事務對應操作的接口。
這里的t.getProcessor() 是需要根據(jù)當前事務的類型(TCC、SAGA、XA)獲取到對應的處理器來進行邏輯的處理。
當然,每個事務處理器只需要實現(xiàn)接口,
真正調用RM資源服務地址的時候,分為http和grpc,這是由開發(fā)者決定的。
在v1.6之前的版本,grpc的請求是很簡單粗暴解析地址方法然后連接的。
現(xiàn)在為了支持那些采用gRPC Resolver 機制之上的一些微服務框架接入,做了一塊抽象。感興趣[1]可以看下,這里就不介紹了。
SDK
至于SDK,每一個事務模式都是獨立的,本質上是沒有關聯(lián)的。比如下面我們啟動一個TCC分布式事務。這個分布式事務是由兩個服務組成,簡稱+30和-30的服務。
- 從上面的調用中我們還是能還原出整體流程。
- 調用TM,得到一個分布式id
- 調用TccGlobalTransaction函數(shù)開啟分布式事務。
- 調用TM prepare(這步只是為了查看第一步產生的那個分布式事務狀態(tài)是否處于prepare。這里沒看明白,此時還未注冊執(zhí)行分支,全局狀態(tài)不是應該只會存在初始化狀態(tài)嗎)
- 上一步沒問題,執(zhí)行傳入的閉包函數(shù),即CallBranch 函數(shù)里向TM注冊參與事務的TM分支。注冊完成后,開始第一階段調用各分支的try服務。
- 各分支try服務調用結束,根據(jù)第一階段結果決定通知TM是submit還是abort。
另外提一點,分布式事務常見的一些問題:比如空補償、重掛等問題。
一般情況下,業(yè)務需要自行去處理這種場景,以免造成不可描述的錯誤。
dtm里面提供了對應子事務屏障方案。核心就在,
其實就是利用數(shù)據(jù)庫的唯一索引機制,當然每個RM資源你都得新增一張表。
上面提到,dtm的TM角色本質上就是對應 seata 中的 TC,但是他們的處理模式是不同的。
dtm中的TM會根據(jù)注冊時的各分支保存的地址,決定通過http還是rpc調用各RM操作,是由TM直接發(fā)起對RM的請求。
seata-go的實現(xiàn)中,TC是不參與直接調用RM的。
還記得上篇提到一個雙向流RPC接口(BranchCommunicate)。TC通過這個接口把對應分支處理信息傳遞給RM管理器。
然后由RM管理器根據(jù)事務類型選擇對應的事務管理器進行處理,最終調用的是對應事務類型管理器的BranchCommit方法。
下面是一個TCC事務類型管理器的處理。
對應的事務RM管理器是如何通知、處理各個RM資源的。
原理就是我上篇提到的作者實現(xiàn)的一個全局事務代理模式,本質上是利用go的反射實現(xiàn)的,感興趣的可以自己去扒下源碼,也可以看看作者對實現(xiàn)全局事務代理的介紹[2]。
總結
這篇文章主要介紹了dtm實現(xiàn)的一些細節(jié),從這兩篇文章大體能看出實現(xiàn)上的部分區(qū)別,更多的細節(jié)還得靠自己去挖掘。
最后再問幾個問題,
- 日常開發(fā)中你們哪些場景是用到了分布式事務?用的是哪個框架還是自研的?
- 或者說在分布式環(huán)境下,一致性的問題你們是如何解決的?
相關
https://zhuanlan.zhihu.com/p/351391359
https://dtm.pub/protocol/support.html