摘要:最終一致性通常使用消息機制來設計,其核心是消息的安全送達與消費。事務狀態,在的不通階段進行事務狀態的變更。除了,最近項目中還涉及了安全消息,等弄清楚了再來一發。
TCC 開源項目源碼學習(一)
學習TCC分布式事務的知識是使用了GIT上的一個開源項目,之前有簡單的看過一些,有了一個大概的了解,但是隨著時間的‘清洗’,又開始變得‘渾濁不清’了,這次索性把這份源碼從頭看了下,又把流程推演了好幾次,覺得已經懂了,想把理解的東西通過博客寫出來。在這個過程中,再次梳理一遍,加深理解,同時也可以發現一些理解偏差和錯誤的地方。
GIT: https://github.com/1755728288...
在分布式系統中,為了保證執行指令的正確性,引入了事務的概念。一般意義上,事務主要突出的是ACID,即原子性,一致性,隔離性和持久性。而在分布式事務中,最主要的原子性的保證,要保證一個業務操作在其分布式系統中的所有指令全部執行或不執行。因為各個指令的操作耗時以及順序,所以原子性都對應了一定的時間窗口,比如單機系統下,這個時間窗口非常短,而且其原子性也由數據庫事務來保證。而在分布式系統中,原子性就依賴于具體的應用設計了,主要的依據是業務上允許的時間窗口的長短。目前一般來說,若允許的時間窗口較短,就用TCC,若允許的時間窗口較長,則使用最終一致性。最終一致性通常使用消息機制來設計,其核心是消息的安全送達與消費。
TCC可以說是一種設計模式,將傳統單機系統中的大事務進行拆分,通過小事務來拼裝,并協調整體的事務提交和回滾。按字面意思理解,TCC主要分Try,Confirm,Cancel三個階段,Try預留資源,Confirm使用預留資源執行業務操作,Cancel則是釋放資源。貌似簡單,但是在實際的業務場景中,往往會困惑我們在這三個階段分別要做什么,這個是業務設計的一部分內容了,在此不展開敘述。不過要注意的一點時,任何一個階段的結果都是可見的,比如一個庫存子服務的入庫方法,在try階段就直接加到庫存上去了,回滾的時候發現剛加上的庫存已經有買家下單準備出庫了,那就GG了。
代碼結構先放個圖,這是項目的組件,上面綠色區域是框架的子模塊,下面黃色的是樣例模塊。不要被黃色區域嚇到,TCC的代碼不多,主要是tcc-transaction-core.
組件名 | 說明 |
---|---|
tcc-transaction-core | 【重要】核心代碼,主要的切面和job |
tcc-transaction-api | 注解和常量 |
tcc-transaction-spring | spring使用的擴展 |
tcc-transaction-dubbo | dubbo使用時的擴展 |
tcc-transaction-server | tcc事務管理控制臺 |
tcc-transaction-unit-test | 單元測試 |
tcc-transaction-tutorial-sample | 訂單場景示例工程,分dubbo和spring版本 |
所以重點只有一個模塊,就是tcc-transaction-core,代碼量不多,主要分成下面的模塊
模塊名 | 說明 |
---|---|
interceptor | 【重要】2個切面,一個是根據事務狀態進行tcc階段方法的選擇,一個是組織事務的參與者,用XID將各個參與者綁起來,以便事務的提交和回滾 |
recover | 恢復在同步調用失敗事務的定時處理 |
repository | 事務對象持久DAO對象,提供了多種選擇:緩存,文件系統,jdbc,zookeeper |
context | 傳遞事務上下文的方法類,一般會在遠程方法調用的切面中,將上下文加入到參數列表中 |
common | 方法枚舉和事務枚舉 |
serializer | 事務對象的序列化器,使用了kyto序列化工具 |
support | 實現了工廠類,管理TCC的類的實例化 |
utils | 工具類 |
最重要的是interceptor,是主要業務邏輯所在。
題外話,有人初看了一遍TCC,拿來和數據庫事務來比較,會說‘TCC不就是業務補償么,沒什么難點啊’,貌似有道理,其實不然。還記得數據庫事務的ACID嗎?因為數據庫提供了事務特性,ACID由數據庫保證,應用只需要一個簡單的@Transactional注解就都搞定了。而分布式事務,就把ACID的特性從數據庫里面拿了出來,由應用程序來保證。當然ACID也有了和之前不一樣的含義。
特性 | 數據庫 | 分布式事務 |
---|---|---|
A[Atomicity] | 所有數據庫更新一起提交和回滾,數據庫通過日志來實現 | 通過協調各個事務的參與方,通過框架來處理異常 |
C[Consistncy] | 通過數據庫約束來實現,比如非空,唯一,外鍵等 | |
I[Isolation] | 各個數據庫實現方式不一樣,主要分讀已提交,讀未提交等 | TCC通過try階段鎖定資源來實現隔離,即業務資源的隔離 |
D[Durability] | 數據庫實現 | 數據庫實現 |
以項目中的例子來說明整個TCC的處理流程,這個例子是類似電商下單的場景,在支付的時候可以選擇紅包或本金,比如一個筆記本4000,可以選擇使用3000的本金和1000的紅包來支付,有三個服務:訂單服務(order),本金服務(capital)和紅包服務(redPacket)。主調方是Order,被調方Capital和RedPacket.
下面是絞盡腦汁畫的調用流程圖,為了圖的簡便直觀,省去了cancel的處理,只有try和confirm的調用(cancel的處理和confirm基本一致)。紅線是try的調用,藍線是confirm的調用。粗紅線是開始,粗藍線是結束。
在try階段的makePayment,方法調用了兩個子事務的api方法,其他的方法的調用均是全局事務切面決定的。業務方法只要按TCC框架的要求實現即可,其他的交給TCC框架。
我們從TCC事務注解開始,先介紹幾個基本的類。
/** 屬性主要是事務傳播屬性,提交方法,回滾方法,上下文傳遞類,是否異步提交,是否異步回滾 propagation屬性比較重要,值有required,support,mandatory和requireNEW,具體的含義和spring的事務傳播類似。 **/ public @interface Compensable { public Propagation propagation() default Propagation.REQUIRED; public String confirmMethod() default ""; public String cancelMethod() default ""; public Class extends TransactionContextEditor> transactionContextEditor() default DefaultTransactionContextEditor.class; public boolean asyncConfirm() default false; public boolean asyncCancel() default false;
/** 事務狀態,在TCC的不通階段進行事務狀態的變更。 **/ public enum TransactionStatus { TRYING(1), CONFIRMING(2), CANCELLING(3);
/** ROOT:事務啟動方法,比如例子中order的makePayment方法 PROVIDER:事務協同方法,比如例子中redpacket的record方法 Normal:普通方法,比如API里面的record方法 **/ public enum MethodType { ROOT, PROVIDER, NORMAL; }
/** ROOT:主事務,一般MethodType為ROOT的啟動 BRANCH:分支事務 **/ public enum TransactionType { ROOT(1), BRANCH(2);
下面我們看一下事務持久的內容,能更好的幫助理解。每個服務都會有一張事務表,會記錄所有的ROOT和BRANCH事務,在事務完成之后,會自動刪除。
TRANSACTION_ID | DOMAIN | GLOBAL_TX_ID | BRANCH_QUALIFIER | CONTENT | STATUS | TRANSACTION_TYPE | RETRIED_COUNT | VERSION |
---|---|---|---|---|---|---|---|---|
2 | ORDER | 事務編號001 | 分支標識A | 事務對象A | 2 | 1 | 0 | 11 |
2 | CAPITAL | 事務編號001 | 分支標識B | 事務對象B | 2 | 2 | 0 | 11 |
2 | REDPACKET | 事務編號001 | 分支標識C | 事務對象C | 2 | 2 | 0 | 11 |
domain區分了服務
global_tx_id 串起了所有的事務
transaction_type 1為主事務,2為分支事務
content 用于恢復事務,主事務的content中包含了參與者
代碼部分的最后是兩個切面和恢復的job,不進行特別的解釋了,將我的理解放在代碼的注釋里面。
//CompensableTransactionInterceptor.java public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable { //獲取所執行事務方法的設置屬性 Method method = CompensableMethodUtils.getCompensableMethod(pjp); Compensable compensable = method.getAnnotation(Compensable.class); Propagation propagation = compensable.propagation(); TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()); boolean asyncConfirm = compensable.asyncConfirm(); boolean asyncCancel = compensable.asyncCancel(); //當前是否存在事務, boolean isTransactionActive = transactionManager.isTransactionActive(); /** * 判斷方法類型 * MethodType一共有三種,Root,Provider,Normal. 主要通過propagation和isTransactionActive來確定 */ MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext); switch (methodType) { case ROOT: //ROOT:(1)propagation為require_new (2)propataion為require,且之前沒有有事務存在 //主事務處理方法 return rootMethodProceed(pjp, asyncConfirm, asyncCancel); case PROVIDER: //PROVIDER:(1) propation為require或者mandatory,且之前有事務 //從事務處理方法 return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel); default: //普通方法執行 return pjp.proceed(); } } private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable { Object returnValue = null; Transaction transaction = null; try { //開啟新的主事務,使用新生成的xid,狀態為trying,事務類型為ROOT transaction = transactionManager.begin(); try { returnValue = pjp.proceed(); } catch (Throwable tryingException) { if (!isDelayCancelException(tryingException)) { logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException); //異常回滾,將事務狀態更新為CANCELLING transactionManager.rollback(asyncCancel); } throw tryingException; } //執行confirm方法,將事務狀態更新為CONFIRMING。如果是異步,則TM會使用線程池異步執行,否則直接調用,會協調所有的參與者進行提交。并將事務記錄刪除 transactionManager.commit(asyncConfirm); } finally { //清理事務數據 transactionManager.cleanAfterCompletion(transaction); } return returnValue; } private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext, boolean asyncConfirm, boolean asyncCancel) throws Throwable { Transaction transaction = null; try { switch (TransactionStatus.valueOf(transactionContext.getStatus())) { case TRYING: //開啟一個子事務,并調用TCC的try方法 transaction = transactionManager.propagationNewBegin(transactionContext); return pjp.proceed(); case CONFIRMING: //獲取子事務TRY階段的事務,并調用TCC的commit方法 try { transaction = transactionManager.propagationExistBegin(transactionContext); transactionManager.commit(asyncConfirm); } catch (NoExistedTransactionException excepton) { //the transaction has been commit,ignore it. } break; case CANCELLING: //獲取子事務保存的事務數據,執行cancel方法 try { transaction = transactionManager.propagationExistBegin(transactionContext); transactionManager.rollback(asyncCancel); } catch (NoExistedTransactionException exception) { //the transaction has been rollback,ignore it. } break; } } finally { //清理事務數據 transactionManager.cleanAfterCompletion(transaction); } Method method = ((MethodSignature) (pjp.getSignature())).getMethod(); return ReflectionUtils.getNullValue(method.getReturnType()); } private boolean isDelayCancelException(Throwable throwable) { if (delayCancelExceptions != null) { for (Class delayCancelException : delayCancelExceptions) { Throwable rootCause = ExceptionUtils.getRootCause(throwable); if (delayCancelException.isAssignableFrom(throwable.getClass()) || (rootCause != null && delayCancelException.isAssignableFrom(rootCause.getClass()))) { return true; } } } return false; }總結
代碼分析的比較少,這份代碼還是有很多值得稱道的地方,工廠類,緩存,模板方法等設計模式的使用,下次可以從設計模式的角度來進行分析。除了TCC,最近項目中還涉及了安全消息,等弄清楚了再來一發。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74067.html
摘要:如上圖所示,的實際上是已中間件的形式放在應用層,不用依賴數據庫對協議的支持,完全剝離了分布式事務方案對數據庫在協議支持上的要求。 微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。 在微服務架構體系下,我們可以按照業務模塊分層設計,單獨部署,減輕了服務部署壓力,也解耦了業務的耦合,避免了應用逐漸變成一個龐然怪物,從而可以輕松擴展,...
摘要:即服務不能無響應,或出錯分區的容忍性,這里的分區不是指數據分布式存儲中的分區。假設一個分布式系統中,有兩個節點,處于分區狀態。在大多數的分布式系統設計中,人們多會選擇滿足兩點特性。為了解決最終的一致性,這就涉及到分布式事務。 showImg(https://segmentfault.com/img/bV7kd4?w=500&h=253); 一、分布式的兩大場景 數據存儲的分布式 服務的...
摘要:即服務不能無響應,或出錯分區的容忍性,這里的分區不是指數據分布式存儲中的分區。假設一個分布式系統中,有兩個節點,處于分區狀態。在大多數的分布式系統設計中,人們多會選擇滿足兩點特性。為了解決最終的一致性,這就涉及到分布式事務。 showImg(https://segmentfault.com/img/bV7kd4?w=500&h=253); 一、分布式的兩大場景 數據存儲的分布式 服務的...
摘要:中大致分為兩部分事務管理器和本地資源管理器。具體實現分布式事務框架的核心功能是對本地事務的協調控制,框架本身并不創建事務,只是對本地事務做協調控制。 Spring Cloud 分布式事務管理 在微服務如火如荼的情況下,越來越多的項目開始嘗試改造成微服務架構,微服務即帶來了項目開發的方便性,又提高了運維難度以及網絡不可靠的概率. @[toc]在說微服務的優缺點時,有對比才會更加明顯,首先...
閱讀 1640·2023-04-25 20:36
閱讀 2049·2021-09-02 15:11
閱讀 1177·2021-08-27 13:13
閱讀 2653·2019-08-30 15:52
閱讀 4589·2019-08-29 17:13
閱讀 1001·2019-08-29 11:09
閱讀 1491·2019-08-26 11:51
閱讀 833·2019-08-26 10:56