EasyTransaction主要原始碼分析

YOYO&#發表於2019-06-30

EasyTransaction是一個全功能的分散式事務框架,以下特性摘抄自其首頁:https://github.com/QNJR-GROUP/EasyTransaction

  • 一個框架包含多種事務形態,一個框架搞定所有型別的事務
  • 多種事務形態可混合使用
  • 高效能,大多數業務系統瓶頸在業務資料庫,若不啟用框架的冪等功能,對業務資料庫的額外消耗僅為寫入25位元組的一行
  • 可選的框架自帶冪等實現及呼叫錯亂次序處理,大幅減輕業務開發工作量,但啟用的同時會在業務資料庫增加一條冪等控制行
  • 業務程式碼可實現完全無入侵
  • 支援巢狀事務
  • 無需額外部署協調者,不同APP的服務協調自身發起的事務,也避免了單點故障
  • 分散式事務ID可關聯業務ID,業務型別,APPID,便於監控各個業務的分散式事務執行情況

本文主要分享EasyTransaction core中各個package的作用其主要實現。

請先閱讀 Seata架構的比對思考 https://www.cnblogs.com/skyesx/p/10674700.html ,再結合 程式碼以及demo除錯過程看這篇,直接看的話這裡的點太零碎了

一、context包

主要類

LogProcessContext

其用於儲存ET事務的上下文資訊。在開啟ET事務(第一次ET遠端呼叫,或者主動呼叫startSoftTrans方法)時,將建立本類的例項並將其與Spring的本地事務上下文繫結,通過:

TransactionSynchronizationManager.bindResource()

執行繫結。當需要取得ET上下文時,通過

TransactionSynchronizationManager.getResource()

取得。

ET上下文中包含的主要內容有:

  • 最終事務狀態
  • 全部的全域性事務日誌
  • 未Flush到外部的全域性事務日誌
  • 事務ID等內容

二、包core

本包主要類為

EasyTransFacade
TransactionHook
ConsistentGuardian
ExecuteCacheManager

類EasyTransFacade

其定義了業務呼叫方的介面,只包含兩個:

public void startEasyTrans(String busCode,long trxId);

public <P extends EasyTransRequest<R,E>,E extends EasyTransExecutor, R extends Serializable> Future<R> execute(P params);

第一個用於開啟全域性事務,主要的操作為:

  • 掛載TransactionHook到當前的Spring本地事務中,使得可以在關鍵節點(如本地事務提交前、本地事務回滾後等等)嵌入ET的程式碼
  • 將 LogProcessContext 繫結到當前的Spring本地事務,使得ET可以在當前Spring本地事務中隨時取得ET全域性事務的狀態。
  • 在當前已開啟的本地事務中,寫入一條事務執行記錄到業務庫中,其對Crash恢復時識別全域性事務的狀態起關鍵作用

第二個表示執行某個遠端事務方法。

  • 通過呼叫引數Object對應的Class獲取對應的處理器(如TCC處理器,可靠訊息處理器等)並執行呼叫,具體呼叫的形態後續專門的章節再繼續

基於註解的介面呼叫也是通過這兩個方法封裝而成。

類TransactionHook

其為ET框架程式碼與Spring原生事務的主要交界點,ET通過TransactionSynchronization定義的方法,在Spring本地事務執行過程中,擴充套件支援了全域性事務。主要擴充套件了以下兩個方法

beforeCommit(boolean readOnly)
afterCompletion(int status)

beforeCommit方法將會

  • 在Spring本地事務提交前將所有未落盤的全域性事務日誌落盤
  • 並執行所有未執行的遠端呼叫(ET會盡量延後全域性事務以此堆積並批量執行)
  • 若有不成功的全域性事務,則丟擲異常,回滾事務(包括本地以及全域性)

afterCompletion方法將會

  • 獲取本地事務的最終結果(提交/回滾/未知)以及 ET父級事務的狀態(提交/回滾/未知)來決定本級ET事務的最終狀態(提交/回滾/未知)
  • 獲得最終的本級ET事務狀態後,非同步執行最終一致處理(呼叫consistentGuardian.process)

類ConsistentGuardian

本類用於處理ET全域性事務的最終一致,例如TCC的Conifrim/Cancel,可靠訊息的傳送訊息。

最終一致處理通常會在同步操作(TCC的TRY等)對應的本地事務執行完成後拋到執行緒池非同步執行,但執行失敗的話,會有兜底的補償(recovery包),後續再詳細講述

該類的主要工作機制是根據之前寫入的全域性事務日誌,獲取日誌對應的處理器(如從TCC的事務日誌獲取對應的TCC日誌處理器),以此

  • 判斷當前ET事務的最終狀態(若當前ET事務狀態仍未確定的話)
  • 傳入最終ET事務狀態到日誌處理器,依次處理對應的事務日誌,處理的典型過程例子:
    • 若存在TRY方法對應的日誌
    • 並且找不到TRY對應的CONFRIM/CANCEL日誌
    • 則根據ET最終事務狀態,呼叫對應CONFIRM/CANCEL方法

類ExecuteCacheManager

本類主要服務於ET的以下期望

  • 批量寫入ET事務日誌(以減少IO)
  • 批量併發執行遠端業務呼叫(以減少序列等待遠端相應時間)

其主要實現的是,

  • 對每個傳入的Calleble物件都返回一個經過改寫的Futrure物件
  • 當任意一個Futrue的get方法都沒有被呼叫前,所有之前傳入的Callable物件都不會執行。
  • 當任意一個Future的get被呼叫時,所有callable都會被批量執行,這裡包含了批量寫入日誌以及批量併發執行遠端呼叫

三、包datasource

主要包含以下兩個介面,其主要作用於業務資料來源。

DataSourceSelector
TransStatusLogger

類DataSourceSelector

該類主要用於獲取當前事務/請求對應的資料來源及其事務管理器,若應用有多個業務資料來源,則需要自行實現對應的資料來源選擇器,主要包含以下方法

DataSource selectDataSource(String appId,String busCode,long trxId);
DataSource selectDataSource(String appId,String busCode,EasyTransRequest<?, ?> request);

第一個方法是開啟ET事務時候選擇對應的資料來源

第二個方法是被呼叫方接受到請求時選擇對應的資料來源(用於冪等、防懸掛處理,若不需要可忽略)

該介面包含一個預設實現,當只有單資料來源時,可以直接用該實現

SingleDataSourceSelector

類TransStatusLogger

該類主要用來讀寫用於判斷ET事務狀態的記錄,該記錄會在ET事務開啟時,寫入當前的資料庫表中,隨著業務對應事務(Spring本地事務)提交而提交,回滾而回滾。

更具體請直接看實現

四、包executor

該包儲存的是事務發起方(遠端服務呼叫方)相關處理類的位置,不同的事務型別(TCC,可靠事務等)有不同的Executor,以TCC為例講解,其他的事務型別實現都類似。

TccMethodExecutor

該類實現了三個介面

  • EasyTransExecutor
  • LogProcessor
  • DemiLogEventHandler

EasyTransExecutor介面定義了方法

    <P extends EasyTransRequest<R,E>,E extends EasyTransExecutor,R  extends Serializable> Future<R> execute(Integer sameBusinessCallSeq, P params);

該方法供類EasyTransFacade.execute使用,其對應的是執行TCC裡的TRY方法,具體的,它

  • 將TRY方法呼叫對應的RPC請求包裝成Runnable類
  • 構建本次呼叫對應的全域性事務日誌(主要包含本次呼叫的具體引數、對應遠端方法等)
  • 然後傳入上面章節提到的類ExecuteCacheManager方法中

LogProcessor介面定義瞭如何處理事務日誌,其包含一個主要方法

    boolean logProcess(LogProcessContext ctx, Content currentContent) 
    

該方法將會判斷,如果傳入的日誌型別是PreTccCallContent(TCC TRY請求對應的日誌)的話,將會監聽該日誌最終的配對資訊(類ConsistentGuardian會在處理當前ET事務的日誌後,傳送訊息,告知所有需要配對的日誌的配對結果),如果

  • 監聽到成功配對(找到CONFIRM或者CANCEL對應的日誌)的訊息,則不再做後續處理
  • 監聽到配對失敗(沒有存在對應的CONFIRM/CANCEL日誌)的訊息,則根據當前的ET事務狀態執行對應的CONFIRM或者CANCEL操作,並記錄對應的日誌

其他的事務形態的實現也類似,不再贅述

五、包recovery

用於兜底恢復事務,實現最終一致。

程式碼不復雜,可以自行檢視。

六、包Filter

該包主要用於實現ET對應的Filter,該Filter作用於被呼叫端。我們可以通過實現ET的Filter擴充套件被呼叫端的功能,如處理冪等、處理巢狀事務、增加呼叫上下文的處理等等。

七、包idempotent

實現冪等、方懸掛等處理對應的包

冪等及防懸掛處理的主要原理:

  • 當遠端呼叫過來時,寫入呼叫日誌到當前的開啟的業務日誌中,並記錄 呼叫對應的ID,呼叫引數對應的MD5
  • 有結果返回時就將結果更新儲存到日誌中
  • 當有重複請求過來時,就檢查ID對應的記錄是否存在,若存在則檢查引數的MD5是否一致,若一致則返回之前的儲存結果
  • 防懸掛也類似,在上述的日誌中,將會記錄呼叫的方法是什麼,如
    • 當找不到請求對應日誌時,但當前為cancel操作的話,框架將直接返回成功
    • 上述cancel已經成功執行後,try方法再來到時,發現cancel已經執行,就直接將try報錯返回

八、包idgen

用於生成ET的分散式事務ID,當自行制定ID時,本包對應的方法不會被呼叫。當不指定ID時,將會自動生成一個。

九、包log

定義ET事務日誌對應的Class,以及其讀寫介面。
事務日誌在之前TccExecutor等章節已提到,不再贅述。

需要擴充套件事務日誌儲存實現的,直接實現以下介面即可

TransactionLogReader
TransactionLogWritter

(ET事務日誌的讀寫不需要與業務事務在同一個事務中,也不能在同一個事務中)

十、包master

用於在同一個appId中選擇一個作為master進行兜底最終一致補償的包。

實際上選擇不需要太精確,任意一個appId下的例項均可,也可以同時有多個master存在(但目前沒有意義,也會浪費效能)

十一、包monitor

用於提供ET例項狀態的包,可供Dashboard,監控等擴充套件使用

十二、包protocol

供客戶直接定義分散式事務服務的包,其包含一些客戶直接使用的 父類、配置介面、配置註解等

十三、包provider.factory

從Spring中獲取並儲存對應的bean實現,以便於快速方便地通過ET對應的定義,取得對應bean

十四、包queue

若要擴充套件新增對應的訊息佇列實現,則實現這個包對應的介面

十五、包rpc

同上

十六、包serialization

ET框架所使用的序列化形式,可自行擴充套件

十七、包stingcodec

用於壓縮字串,將字串替換為數字id,以提高儲存效率

相關文章