開源源碼地址:https://github.com/changmingxie/tcc-transaction.git
項目demo地址:https://github.com/liuchaoOvO/tcc-transaction-demo.git
源碼修改成springboot 源碼地址:https://github.com/liuchaoOvO/tcc-transaction.git
主要涉及如下三個 Maven 項目:
-
tcc-transaction-core
:tcc-transaction 底層實現。 -
tcc-transaction-api
:tcc-transaction 使用 API。 -
tcc-transaction-spring
:tcc-transaction Spring 支持。
目錄
TCC事務簡介
爲了解決在事務運行過程中,大顆粒度資源鎖定的問題,提出一種新的事務模型,它是基於業務層面的事務定義。鎖粒度完全由業務自己控制。它本質是一種補償的思路。它把事務運行過程分成 Try、Confirm / Cancel 兩個階段。在每個階段的邏輯由業務代碼控制。這樣就事務的鎖粒度可以完全自由控制。業務可以在犧牲隔離性的情況下,獲取更高的性能。本質上,TCC 通過多個參與者的 try / confirm / cancel 方法,實現事務的最終一致性。
架構模型圖
-
Try 階段
-
完成所有業務檢查( 一致性 )
-
預留必須業務資源( 準隔離性 )
-
Try :嘗試執行業務
-
-
Confirm / Cancel 階段:
-
釋放 Try 階段預留的業務資源
-
Cancel 操作滿足冪等性(先getXXXID()得到對應Try 嘗試保存的數據,再補償取消)
-
真正執行業務
-
不做任務業務檢查
-
Confirm 操作滿足冪等性
-
Confirm :確認執行業務
-
Cancel :取消執行業務
-
Confirm 與 Cancel 互斥
-
與 2PC協議 比較
-
沒有單獨的準備( Prepare )階段,Try 操作兼備自願操作與準備能力
-
Try 操作可以靈活選擇業務資源的鎖定粒度
-
較高開發成本
TCC柔性補償性事務:嘗試/ 確定/ 取消 三個接口都要我們自己去實現
在 TCC 裏,一個業務活動可以有多個事務,每個業務操作歸屬於不同的事務,即一個事務可以包含多個業務操作。TCC-Transaction 將每個業務操作抽象成事務參與者,每個事務可以包含多個參與者。
TCC-Transaction 有兩個攔截器,通過對 @Compensable AOP 切面( 參與者 try 方法 )進行攔截,透明化對參與者 confirm / cancel 方法調用,從而實現 TCC 。
兩種攔截器:
可補償事務攔截器:CompensableTransactionInterceptor
資源協調者攔截器:ResourceCoordinatorInterceptor
第一個攔截器,可補償事務攔截器,實現如下功能:
-
在 Try 階段,對事務的發起、傳播。
-
在 Confirm / Cancel 階段,對事務提交或回滾。
-
爲什麼會有對事務的傳播呢? 在遠程調用服務的參與者時,會通過"參數"( 需要序列化 )的形式傳遞事務給遠程參與者。
第二個攔截器,資源協調者攔截器,實現如下功能:
-
在 Try 階段,添加參與者到事務中。當事務上下文不存在時,進行創建。
源碼
1、參與者對象 Participant :
public class Participant implements Serializable {
/**
* 事務編號
*/
private TransactionXid xid;
/**
* 確認執行業務方法調用上下文
*/
private InvocationContext confirmInvocationContext;
/**
* 取消執行業務方法
*/
private InvocationContext cancelInvocationContext;
/**
* 執行器
*/
private Terminator terminator = new Terminator();
/**
* 事務上下文編輯
*/
Class<? extends TransactionContextEditor> transactionContextEditorClass;
/**
* 提交事務
*/
public void commit() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}
/**
* 回滾事務
*/
public void rollback() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass);
}
...
}
2、InvocationContext。
執行方法調用上下文,記錄類、方法名、參數類型數組、參數數組。通過這些屬性,可以執行提交 / 回滾事務。在 org.mengyun.tcctransaction.Terminator
會看到具體的代碼實現。
3、Terminator 執行器
public class Terminator implements Serializable {
private static final long serialVersionUID = -164958655471605778L;
public Object invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) {
if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {
try {
// 獲得 參與者對象
Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance();
// 獲得 方法
Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
// 設置 事務上下文 到方法參數
FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs());
// 執行方法
return method.invoke(target, invocationContext.getArgs());
} catch (Exception e) {
throw new SystemException(e);
}
}
return null;
}
4、事務管理器 TransactionManager
事務管理器不關心接口是怎麼實現的,只會根據情況去調用。1、啓動事務。2、調用嘗試的接口,3、提交或者失敗調用取消接口。
TransactionManager
發起根事務
transactionManager 提供 begin()
方法,發起根事務。該方法在調用方法類型爲 MethodType.ROOT 並且 事務處於 Try 階段被調用。
TCC-Transaction 支持多個的事務獨立存在,後創建的事務先提交(原因:ThreadLocal<Deque<Transaction>>),類似 Spring 的org.springframework.transaction.annotation.Propagation.REQUIRES_NEW
傳播發起分支事務
transactionManager 調用 #propagationNewBegin(...)
方法,傳播發起分支事務。該方法在調用方法類型爲 MethodType.PROVIDER 並且 事務處於 Try 階段被調用。
傳播獲取分支事務
transactionManager 調用 #propagationExistBegin(...)
方法,傳播發起分支事務。該方法在調用方法類型爲 MethodType.PROVIDER 並且 事務處於 Confirm / Cancel 階段被調用。
提交事務
transactionManager 調用 #commit(...)
方法,提交事務。該方法在事務處於 Confirm / Cancel 階段被調用。
回滾事務
transactionManager 調用 #rollback(...)
方法,取消事務,和 #commit()
方法基本類似。該方法在事務處於 Confirm / Cancel 階段被調用。
添加參與者到事務
調用 #enlistParticipant(...)
方法,添加參與者到事務。該方法在事務處於 Try 階段被調用。
5、事務攔截器方法
package org.mengyun.tcctransaction.interceptor;
public class CompensableTransactionInterceptor {
private TransactionManager transactionManager;
private Set<Class<? extends Exception>> delayCancelExceptions;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setDelayCancelExceptions(Set<Class<? extends Exception>> delayCancelExceptions) {
this.delayCancelExceptions = delayCancelExceptions;
}
//事務攔截器通過AOP環繞到加了註解@Compensable的方法,環繞執行下面的方法
public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
//獲取到加了註解的方法本身
Method method = CompensableMethodUtils.getCompensableMethod(pjp);
//獲取到註解@Compensable自己本身 --> 獲取註解的屬性:取消的方法,確認的方法
Compensable compensable = method.getAnnotation(Compensable.class);
Propagation propagation = compensable.propagation();
//獲取事務上下文,RPC隱式參數中包含這個上下文
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();
if (!TransactionUtils.isLegalTransactionContext(isTransactionActive, propagation, transactionContext)) {
throw new SystemException("no active compensable transaction while propagation is mandatory for method " + method.getName());
}
MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);
/*
*這個方法判斷:(!isTransactionActive && transactionContext == null)是否已經有事務
了,事務上下文是否爲空
都滿足說明式主事務,否則是分支事務
*/
switch (methodType) {
case ROOT:
return rootMethodProceed(pjp, asyncConfirm, asyncCancel);
case PROVIDER:
return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel);
default:
return pjp.proceed();
}
}
/**這裏是主事務的操作
開啓事務
註冊,持久化事務
然後判斷是cancel,confirm
清除事務
*/
private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable {
Object returnValue = null;
Transaction transaction = null;
try {
事務爲空那麼就開啓一個全新的事務
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);
transactionManager.rollback(asyncCancel);
}
throw tryingException;
}
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:
transaction = transactionManager.propagationNewBegin(transactionContext);
return pjp.proceed();
case CONFIRMING:
try {
transaction = transactionManager.propagationExistBegin(transactionContext);
transactionManager.commit(asyncConfirm);
} catch (NoExistedTransactionException excepton) {
//the transaction has been commit,ignore it.
}
break;
case CANCELLING:
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-transaction的使用方法
1、需要提供分佈式事務支持的接口上添加@Compensable
2、在對應的接口實現上添加@Compensable
3、在接口實現上添加confirmMethod、cancelMethod、transactionContextEditor
4、實現對應的confirmMethod、cancelMethod 注意:confirm方法和cancel方法必須與try方法在同一個類中.。(根據類的反射找到方法invoke)
注意:
1、分佈式事務裏,不要輕易在業務層捕獲所有異常,拋出異常才能被TCC感知到,然後回調。只捕獲必要捕獲的異常。
2、使用TCC-Transaction時,confirm和cancel的冪等性需要自己編寫代碼支持