擼明白分佈式事務(三)

前言

前面說的二階段提交協議和三階段提交協議很好的解決了分佈式事務的問題,但是在極端情況下仍然存在數據的不一致性,此外它對系統的開銷會比較大,引入事務管理者(協調者)後,比較容易出現單點瓶頸,以及在業務規模不斷變大的情況下,系統可伸縮性也會存在問題。注意的是,它是同步操作,因此引入事務後,直到全局事務結束才能釋放資源,性能可能是一個很大的問題。因此,在高併發場景下很少使用。因此,阿里提出了另外一種解決方案:TCC 模式。注意的是,很多讀者把二階段提交等同於二階段提交協議,這個是一個誤區,事實上,TCC 模式也是一種二階段提交。

TCC 模式

TCC 模式將一個任務拆分三個操作:Try、Confirm、Cancel。假如,我們有一個 func() 方法,那麼在 TCC 模式中,它就變成了 tryFunc()、confirmFunc()、cancelFunc() 三個方法。

tryFunc();
confirmFunc();
cancelFunc();

在 TCC 模式中,主業務服務負責發起流程,而從業務服務提供 TCC 模式的 Try、Confirm、Cancel 三個操作。其中,還有一個事務管理器的角色負責控制事務的一致性。例如,我們現在有三個業務服務:交易服務,庫存服務,支付服務。用戶選商品,下訂單,緊接着選擇支付方式進行付款,然後這筆請求,交易服務會先調用庫存服務扣庫存,然後交易服務再調用支付服務進行相關的支付操作,然後支付服務會請求第三方支付平臺創建交易並扣款,這裏,交易服務就是主業務服務,而庫存服務和支付服務是從業務服務。

 我們再來梳理下,TCC 模式的流程。第一階段主業務服務調用全部的從業務服務的 Try 操作,並且事務管理器記錄操作日誌。第二階段,當全部從業務服務都成功時,再執行 Confirm 操作,否則會執行 Cancel 逆操作進行回滾。

現在,我們針對 TCC 模式說說大致業務上的實現思路。首先,交易服務(主業務服務)會向事務管理器註冊並啓動事務。其實,事務管理器是一個概念上的全局事務管理機制,可以是一個內嵌於主業務服務的業務邏輯,或者抽離出的一個 TCC 框架。事實上,它會生成全局事務 ID 用於記錄整個事務鏈路,並且實現了一套嵌套事務的處理邏輯。當主業務服務調用全部的從業務服務的 try 操作,事務管理器利用本地事務記錄相關事務日誌,這個案例中,它記錄了調用庫存服務的動作記錄,以及調用支付服務的動作記錄,並將其狀態設置成“預提交”狀態。這裏,調用從業務服務的 Try 操作就是核心的業務代碼。那麼, Try 操作怎麼和它相對應的 Confirm、Cancel 操作綁定呢?其實,我們可以編寫配置文件建立綁定關係,或者通過 Spring 的註解添加 confirm 和 cancel 兩個參數也是不錯的選擇。當全部從業務服務都成功時,由事務管理器通過 TCC 事務上下文切面執行 Confirm 操作,將其狀態設置成“成功”狀態,否則執行 Cancel 操作將其狀態設置成“預提交”狀態,然後進行重試。因此,TCC 模式通過補償的方式保證其最終一致性。

TCC 的實現框架有很多成熟的開源項目,例如 tcc-transaction 框架。(關於 tcc-transaction 框架的細節,可以閱讀:https://github.com/changmingxie/tcc-transaction)tcc-transaction 框架主要涉及 tcc-transaction-core、tcc-transaction-api、tcc-transaction-spring 三個模塊。其中,tcc-transaction-core 是 tcc-transaction 的底層實現,tcc-transaction-api 是 tcc-transaction 使用的 API,tcc-transaction-spring 是 tcc-transaction 的 Spring 支持。 tcc-transaction 將每個業務操作抽象成事務參與者,每個事務可以包含多個參與者。參與者需要聲明 try / confirm / cancel 三個類型的方法。這裏,我們通過 @Compensable 註解標記在 try 方法上,並定義相應的 confirm / cancel 方法。

// try 方法
@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", 
transactionContextEditor = MethodTransactionContextEditor.class)
@Transactional
public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
 
// confirm 方法
@Transactional
public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
 
// cancel 方法
@Transactional
public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}

對於 tcc-transaction 框架的實現,我們來了解一些核心思路。tcc-transaction 框架通過 @Compensable 切面進行攔截,可以透明化對參與者 confirm / cancel 方法調用,從而實現 TCC 模式。這裏,tcc-transaction 有兩個攔截器,請參見圖 6-10。

  • org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor,可補償事務攔截器。
  • org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor,資源協調者攔截器。

這裏,需要特別關注 TransactionContext 事務上下文,因爲我們需要遠程調用服務的參與者時通過參數的形式傳遞事務給遠程參與者。在 tcc-transaction 中,一個事務org.mengyun.tcctransaction.Transaction可以有多個參與者org.mengyun.tcctransaction.Participant 參與業務活動。其中,事務編號 TransactionXid 用於唯一標識一個事務,它使用 UUID 算法生成,保證唯一性。當參與者進行遠程調用時,遠程的分支事務的事務編號等於該參與者的事務編號。通過事務編號的關聯 TCC confirm / cancel 方法,使用參與者的事務編號和遠程的分支事務進行關聯,從而實現事務的提交和回滾。事務狀態 TransactionStatus 包含 : 嘗試中狀態 TRYING(1)、確認中狀態 CONFIRMING(2)、取消中狀態 CANCELLING(3)。此外,事務類型 TransactionType 包含 : 根事務 ROOT(1)、分支事務 BRANCH(2)。當調用 TransactionManager#begin() 發起根事務時,類型爲 MethodType.ROOT,並且事務 try 方法被調用。調用 TransactionManager#propagationNewBegin() 方法,傳播發起分支事務。該方法在調用方法類型爲 MethodType.PROVIDER 並且 事務 try 方法被調用。調用 TransactionManager#commit() 方法提交事務。該方法在事務處於 confirm / cancel 方法被調用。類似地,調用 TransactionManager#rollback() 方法,取消事務。

此外,對於事務恢復機制,tcc-transaction 框架基於 Quartz 實現調度,按照一定頻率對事務進行重試,直到事務完成或超過最大重試次數。如果單個事務超過最大重試次數時,tcc-transaction 框架不再重試,此時需要手工介入解決。

這裏,我們要特別注意操作的冪等性。冪等機制的核心是保證資源唯一性,例如重複提交或服務端的多次重試只會產生一份結果。支付場景、退款場景,涉及金錢的交易不能出現多次扣款等問題。事實上,查詢接口用於獲取資源,因爲它只是查詢數據而不會影響到資源的變化,因此不管調用多少次接口,資源都不會改變,所以是它是冪等的。而新增接口是非冪等的,因爲調用接口多次,它都將會產生資源的變化。因此,我們需要在出現重複提交時進行冪等處理。那麼,如何保證冪等機制呢?事實上,我們有很多實現方案。其中,一種方案就是常見的創建唯一索引。在數據庫中針對我們需要約束的資源字段創建唯一索引,可以防止插入重複的數據。但是,遇到分庫分表的情況是,唯一索引也就不那麼好使了,此時,我們可以先查詢一次數據庫,然後判斷是否約束的資源字段存在重複,沒有的重複時再進行插入操作。注意的是,爲了避免併發場景,我們可以通過鎖機制,例如悲觀鎖與樂觀鎖保證數據的唯一性。這裏,分佈式鎖是一種經常使用的方案,它通常情況下是一種悲觀鎖的實現。但是,很多人經常把悲觀鎖、樂觀鎖、分佈式鎖當作冪等機制的解決方案,這個是不正確的。除此之外,我們還可以引入狀態機,通過狀態機進行狀態的約束以及狀態跳轉,確保同一個業務的流程化執行,從而實現數據冪等。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章