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