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