Seata-go TCC 設(shè)計與實現(xiàn)
本文主要介紹 seata-go 中 TCC 的設(shè)計思路、異常處理以及在實戰(zhàn)中的使用。
Seata 是一款開源的分布式事務(wù)解決方案,致力于為現(xiàn)代化微服務(wù)架構(gòu)下的分布式事務(wù)提供高性能和簡單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 等多種事務(wù)模式,幫助用戶解決不同場景下的業(yè)務(wù)問題。同時,Seata 還支持多語言編程,并且提供了簡易的 API 接口、豐富的文檔以及快速上手的 samples 示例項目,也能快速幫助開發(fā)者入門并上手 Seata 的使用。
Seata-go 是 Seata 多語言生態(tài)中 golang 語言的實現(xiàn)方案,它致力于幫助 golang 開發(fā)者也能使用 Seata 的能力來解決分布式事務(wù)場景的問題。Seata-go 復(fù)用了 Seata TC 的能力,client 的功能和 Seata 保持一致。目前 Seata-go 已經(jīng)支持了 TCC 和 AT 模式,XA 模式正在測試中,預(yù)計會在 5 月份發(fā)版。Saga 模式正在設(shè)計和規(guī)劃中,后面也會和 Seata 的 Saga 功能保持一致。
本文主要從以下幾個角度,介紹 Seata-go 中的 TCC 模式的設(shè)計與使用:
Seata-go TCC 實現(xiàn)原理
Sata-go TCC 異常處理
Seata-go 的展望
Seata-go TCC 實現(xiàn)原理
Seata-go 采用了 getty 做 TCP 網(wǎng)絡(luò)通信,完全實現(xiàn)了 Seata 的通信協(xié)議。下層實現(xiàn)了配置中心和注冊中心,也支持了很多的第三方框架的接入,比如 dubbo、grpc、gorm 等等,目前也正在積極和各個社區(qū)溝通,以支持更多框架的接入。Seata-go 簡易的系統(tǒng)架構(gòu)圖如下:

先來簡單回顧下 TCC 模式的含義。TCC 是分布式事務(wù)方案的一種實現(xiàn),它采用了二階段提交協(xié)議,TCC 的全稱是 Try-Confirm-Cancel,Try 是預(yù)留資源操作,Confirm 是提交操作,Cancel 是回滾操作。在 TCC 的一階段中,先觸發(fā)所有的子事務(wù)執(zhí)行 Try 操作,如果所有的子事務(wù)的一階段都執(zhí)行成功,那么會觸發(fā)所有子事務(wù)二階段執(zhí)行 Confirm 操作,否則二階段執(zhí)行 Cancel 操作,以此來保證各個子事務(wù)狀態(tài)的一致性。
TCC 是一種侵入式的分布式事務(wù)方案,Try、Confirm 和 Cancel 三個階段的邏輯,都需要用戶自己去實現(xiàn)。這樣做意味著更多的代碼量,以及對業(yè)務(wù)很大的入侵性;而優(yōu)點是則比較靈活,能由用戶隨意發(fā)揮以解決更復(fù)雜的分布式事務(wù)場景的問題。
在介紹 Seata-go 的 TCC 模式之前,先來回顧下 Seata 中的三個核心角色,即 TC、TM 和 RM。TC 是事務(wù)協(xié)調(diào)者,負責(zé)維護全局事務(wù)的狀態(tài),以及觸發(fā)分支事務(wù)的提交和回滾動作;TM 是事務(wù)管理器,負責(zé)子事務(wù)的編排,以及全局事務(wù)的提交和回滾動作;RM 是資源管理器,管理分支事務(wù)處理的資源,比如 MySQL 數(shù)據(jù)庫的操作等。
了解了這三個核心角色,就可以大致的理解下 TCC 的事務(wù)流程,大致分為以下幾個步驟:
- TM 向 TC 發(fā)送請求,開啟全局事務(wù),TC 側(cè)記錄下全局事務(wù)的狀態(tài)信息;
 - TM 分別向所有的 RM 發(fā)送請求,RM 會向 TC 注冊分支事務(wù),然后執(zhí)行 Try 階段的邏輯;
 - 如果當(dāng)中某個 RM 給 TM 返回 Try 階段執(zhí)行失敗,那 TM 就向 TC 發(fā)送“回滾全局事務(wù)” 的請求。TC 收到后,就會向所有已執(zhí)行 Try 的 RM 發(fā)送 Rollback 指令,觸發(fā) RM 執(zhí)行 Cancel 邏輯;
 - 如果所有的 RM 都給 TM 返回 Try 階段執(zhí)行成功,那 TM 就向 TC 發(fā)送“提交全局事務(wù)” 的請求。TC 收到后,就會向所有已執(zhí)行 Try 的 RM 發(fā)送 Commit 指令,觸發(fā) RM 執(zhí)行 Commit 邏輯。
 
至此,一個完整的分布式事務(wù)就執(zhí)行完了,以下是這個過程的流程圖:

在 Seata-go 中,為了方便用戶使用,提供了兩種定義 TCC 服務(wù)方法,一種是實現(xiàn) TwoPhaseInterface 接口,具體如下:

另一種是通過 tag 的方式來定義 TCC 服務(wù),這種方式會相對復(fù)雜點,但是也更加的靈活:

第二種 tag 的方案,主要是為了滿足一些特殊的場景,比如說,dubbo-go 的 server 和 client 是使用 tag 的方式來定義的,這個時候就需要使用 tag 的方式來定義 TCC 的服務(wù)。一般情況推薦使用第一種繼承接口的方式來做,比較簡單。
在實際使用的時候,用戶只需要做以下幾件事情即可:
- 定義好自己的 TCC 服務(wù),可以參考上面介紹的這兩種方式之一都可以;
 - 調(diào)用 TCC 的代理方法 NewTCCServiceProxy ,將 TCC 服務(wù)的封裝成代理;
 - 編排好自己的子事務(wù),傳入到分布式事務(wù)的入口方法 WithGlobalTx 方法即可。
 
這里截圖給大家看個例子,更詳細的 samples 請參考 seata-go-samples 項目,地址為:
https://github.com/seata/seata-go-samples

Seata-go TCC 異常處理
在實際使用 TCC 的時候,由于網(wǎng)絡(luò)或是業(yè)務(wù)代碼邏輯執(zhí)行時間等因素,可能會出現(xiàn)以下的問題:
- 冪等:在事務(wù)的一、二階段,由于網(wǎng)絡(luò)延遲或是其他原因,RM 沒有及時給 TC 或 TM 響應(yīng),導(dǎo)致 RM 被重復(fù)觸發(fā)執(zhí)行一、二階段的邏輯,這個時候,需要考慮業(yè)務(wù)的冪等;
 - 空回滾:由于網(wǎng)絡(luò)延遲或是其他原因,RM 在未收到 Try 請求的情況下,卻收到了 Rollback 請求,造成空回滾的問題;
 - 懸掛:由于網(wǎng)絡(luò)延遲或是其他原因,RM 在未收到 Try 請求的情況下,收到了 Rollback 請求,處理完 Rollback 請求后,又收到了 Try 請求。這時全局事務(wù)已結(jié)束,會導(dǎo)致事務(wù)預(yù)留的資源一直無法釋放。
 
在 Seata-go 中,提供了兩種解決方案,來幫助用戶解決這個問題。
第一種方式的原理和 Seata Java 的處理邏輯是一樣的,都是借助 tcc_fence_log 事務(wù)狀態(tài)表來做的:

用戶需要在自己的業(yè)務(wù)數(shù)據(jù)庫中,創(chuàng)建這個表,RM 在提交業(yè)務(wù) SQL 的時候,同時會在這個表里面插入一條記錄,這倆 SQL 是在一個本地事務(wù)中完成的。由于這個表中,“全局事務(wù)ID+分支事務(wù)ID”是一個聯(lián)合主鍵,導(dǎo)致重復(fù)執(zhí)行時會失敗,這樣就解決了 Try 階段的冪等問題。在 Commit 和 Cancel 階段時,會先查詢這個表中分支事務(wù)的狀態(tài),然后才進行實際的邏輯,最后再更新狀態(tài)。這樣也能保證 Commit 和 Cancel 階段的冪等性。
再來看看 Seata-go 是如何解決事務(wù)懸掛和空回滾的問題。假如一個 Rollbback 請求過來,RM 去查詢 tcc_fence_log 表,發(fā)現(xiàn)沒有記錄(因為 RM 尚未收到 Try 請求),此時會往 tcc_fence_log 表插入一條記錄,并標(biāo)記狀態(tài)為 suspend,然后直接退出,而不會去執(zhí)行 Rollback 的邏輯,這樣就避免了空回滾的問題。如果 RM 后面再收到 Try 請求,由于 tcc_fence_log 表已經(jīng)有一條記錄,就會導(dǎo)致事務(wù) SQL 無法提交而失?。╰cc_fence_log 會出現(xiàn)主鍵沖突的問題),這樣就避免了防懸掛的問題。
要實現(xiàn)這種方式,需要使用 Seata-go 提供的代理數(shù)據(jù)源,這些操作都會由代理數(shù)據(jù)源來完成,用戶只需要開啟開關(guān),關(guān)注自己的業(yè)務(wù) SQL 即可,這個功能已經(jīng)實現(xiàn),會在后續(xù)進行發(fā)版。
第二種方式,是通過用戶手動的方式來實現(xiàn)的。原理和上面類似,但是 tcc_fence_log 的操作邏輯需要由用戶自己實現(xiàn),下面的截圖描述了大致的使用方式,詳情可以參考這個 samples 代碼:
https://github.com/seata/seata-go-samples/tree/main/tcc/fence

Seata-go 展望
Seata-go 社區(qū)近期與不少國內(nèi) go 語言微服務(wù)框架以及 ORM 框架背后的開發(fā)社區(qū)達成合作,比如 GORM 框架,已經(jīng)集成到了 Sample 中,后續(xù)會將更多的 ORM 框架集成在 Seata-go-Samples 項目中。與 MOSN 社區(qū)的合作也在推進中,可實現(xiàn)真正的基于 Seata 的 Transaction Mesh。
Seata-go 的 XA 模式會在5月份進行發(fā)版,屆時 Seata-go 將支持 TCC、XA 和 AT 三種事務(wù)模式。Seata-go 后續(xù)的中心將會在 Saga 模式功能的開發(fā)上。
當(dāng)前的 Saga 模式僅實現(xiàn)了服務(wù)編排的正向推進與反向 Rollback 能力,更進一步的服務(wù)編排則可以實現(xiàn) DAG、定時任務(wù)、任務(wù)批量調(diào)度,覆蓋工作流的所有流程,提升用戶在 Seata 這個平臺上的使用體驗。目前 Seata-go 依賴于 Seata Java 的 TC,按照這個工作計劃,可能需要在未來的 Seata-go 版本中實現(xiàn)一個功能更強大的 TC 調(diào)度。















 
 
 














 
 
 
 