版本:1.2.0
TCC是一種資源服務化的兩階段提交協議,用戶需要實現Try、Confirm/Cancel接口。通過一階段的Try操作決定執行Confirm(確認)或者Cancel(取消)操作。
Seata TCC三種接口去對資源進行管理
- prepare資源預留
- commit資源提交
- rollback 資源回滾
然後將分支事務納入到全局事務中,對全局事務管理。
使用
通過與Spring的結合使用瞭解Seata TCC模式的執行流程。
- 定義接口,通過實現接口去進行資源的預留提交和回滾操作,每個分支事務都是RM
public interface TccActionOne {
@TwoPhaseBusinessAction(name="TccActionOne",commitMethod = "commit",rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext,int num);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
每個預留資源接口添加@TwoPhaseBusinessAction(name="TccActionOne",commitMethod = "commit",rollbackMethod = "rollback")
註解去定義各接口操作
- 調用接口,添加
@GlobalTransactional
將分支事務納入全局事務
@GlobalTransactional
public String doTransactionCommit(){
//第一個TCC 事務參與者
boolean result = tccActionOne.prepare(null, 1);
if(!result){
throw new RuntimeException("TccActionOne failed.");
}
//第二個TCC 事務參與者
result = tccActionTwo.prepare(null, "two");
if(!result){
throw new RuntimeException("TccActionTwo failed.");
}
return RootContext.getXID();
}
- 注入
GlobalTransactionScanner
,初始化TMClinet和RmClient及其他模式配置的初始化
@Bean
public GlobalTransactionScanner globalTransactionScanner(){
return new GlobalTransactionScanner("ijcf_tx_group");
}
初始化
通過GlobalTransactionScanner
進行初始化
public class GlobalTransactionScanner extends AbstractAutoProxyCreator
implements InitializingBean, ApplicationContextAware,
DisposableBean
- 實現
InitializingBean
的afterPropertiesSet
方法,會在類實例化時執行,主要是用於初始化TM\RM客戶端並向Server註冊
public void afterPropertiesSet() {
initClient();
}
private void initClient() {
if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
}
//init TM
TMClient.init(applicationId, txServiceGroup);
//init RM
RMClient.init(applicationId, txServiceGroup);
//註冊鉤子
registerSpringShutdownHook();
}
- 實現
ApplicationContextAware
的setApplicationContext
方法獲得spring容器 - 實現
DisposableBean
的destroy
方法,在容器關閉時進行操作 - 繼承
AbstractAutoProxyCreator
類重寫wrapIfNecessary
進行切面代理。
@Override
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//是否禁用了全局事務
if (disableGlobalTransaction) {
return bean;
}
try {
synchronized (PROXYED_SET) {
//檢查當前beanName是否已經處理過 如果處理過本次就不處理
if (PROXYED_SET.contains(beanName)) {
return bean;
}
interceptor = null;
//check TCC proxy
//校驗是否爲TCC分支事務類
//會在這裏進行資源的註冊
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
//創建攔截器
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
} else {
Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
//判斷類方法中是否添加@GlobalTransactional 或@GlobalLock註解,判斷當前是否全局事務類
if (!existsAnnotation(new Class[]{serviceInterface})
&& !existsAnnotation(interfacesIfJdk)) {
return bean;
}
if (interceptor == null) {
//創建全局事務攔截器
interceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (ConfigurationChangeListener) interceptor);
}
}
LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
if (!AopUtils.isAopProxy(bean)) {
//執行父類方法 添加 攔截器
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
for (Advisor avr : advisor) {
advised.addAdvisor(0, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
} catch (Exception exx) {
throw new RuntimeException(exx);
}
}
通過上面的源碼可以看出有兩個核心攔截器,TccActionInterceptor
TCC模式攔截器和GlobalTransactionalInterceptor
全局事務攔截器,這兩個攔截器爲方法攔截器,會在類調用方法時執行其invoke
方法。
TCC流程
調用doTransactionCommit
方法開始分佈式事務執行。通過上面分析會執行GlobalTransactionalInterceptor
全局事務攔截器的invoke
方法
GlobalTransactionalInterceptor.invoke
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
Class<?> targetClass = methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis())
: null;
Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
final GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, GlobalTransactional.class);
final GlobalLock globalLockAnnotation = getAnnotation(method, GlobalLock.class);
//GlobalTransactional全局事務註解切面邏輯
if (!disable && globalTransactionalAnnotation != null) {
return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
} else if (!disable && globalLockAnnotation != null) {
//GlobalLock 註解切面邏輯
return handleGlobalLock(methodInvocation);
} else {
return methodInvocation.proceed();
}
}
注:GlobalLock
註解是爲了保證事務隔離,並不會加入到全局事務
GlobalTransactionalInterceptor.handleGlobalTransaction
private Object handleGlobalTransaction(final MethodInvocation methodInvocation,
final GlobalTransactional globalTrxAnno) throws Throwable {
try {
return transactionalTemplate.execute(new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
...
});
} catch (TransactionalExecutor.ExecutionException e) {
...
}
}
通過TransactionalExecutor
執行方法,而TransactionalTemplate.execute
纔是開始事務的關鍵。
transactionalTemplate.execute
public Object execute(TransactionalExecutor business) throws Throwable {
...
// 1.1 獲取全局事務如果不存在則創建
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
...
try {
...
try {
// 2. 開啓事務
beginTransaction(txInfo, tx);
Object rs = null;
try {
// 2.1執行業務代碼
rs = business.execute();
} catch (Throwable ex) {
// 3.異常回滾
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. 提交
commitTransaction(tx);
return rs;
} finally {
//5. 清理執行結束鉤子
triggerAfterCompletion();
cleanUp();
}
}
主要流程:
- 1.創建全局事務
- 2.TM向TC發送請求開啓全局事務
- 3.執行業務代碼
- 4.根據執行結果回滾或提交
開啓事務
TransactionalTemplate.beginTransaction
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
...
//執行前鉤子
triggerBeforeBegin();
//開啓事務
tx.begin(txInfo.getTimeOut(), txInfo.getName());
//執行後鉤子
triggerAfterBegin();
...
}
GlobalTransaction.begin
public void begin(int timeout, String name) throws TransactionException {
...
// 通過TransactionManager開啓事務
xid = transactionManager.begin(null, null, name, timeout);
status = GlobalStatus.Begin;
//綁定到RootContext
RootContext.bind(xid);
...
}
開啓全局事務
TransactionManager.begin
:TransactionManager,TM 用於開啓和管理全局事務,默認實現DefaultTransactionManager
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
//創建一個全局事務開啓請求
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
//向服務端發送全局事務開啓請求,服務端會返回全局事務ID XID
GlobalBeginResponse response = (GlobalBeginResponse)syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
上篇文章介紹了服務端DefaultCore
是核心實現,驅動全局事務的開啓提交回滾操作。服務端接收請求響應
DefaultCore.begin
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
//創建一個全局事務上下文
GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
timeout);
session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
//開啓事務
session.begin();
eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
session.getTransactionName(), session.getBeginTime(), null, session.getStatus()));
//返回全局事務ID XID
return session.getXid();
}
執行業務代碼
**
上文介紹在TCC分支會創建攔截器TccActionInterceptor
,在執行分支的業務代碼時會調用其invoke
方法。
public Object invoke(final MethodInvocation invocation) throws Throwable {
if (!RootContext.inGlobalTransaction()) {
//不在事務中
return invocation.proceed();
}
Method method = getActionInterfaceMethod(invocation);
//獲取TwoPhaseBusinessAction註解信息
TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
//執行try操作
if (businessAction != null) {
String xid = RootContext.getXID();
//清理核心上文
RootContext.unbind();
RootContext.bindInterceptorType(xid, BranchType.TCC);
try {
Object[] methodArgs = invocation.getArguments();
//攔截器Handler 進行分支註冊及程序執行
Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
invocation::proceed);
//返回結果
return ret.get(Constants.TCC_METHOD_RESULT);
} finally {
//恢復核心上下文
RootContext.unbindInterceptorType();
RootContext.bind(xid);
}
}
return invocation.proceed();
}
ActionInterceptorHandler.proceed
public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
Callback<Object> targetCallback) throws Throwable {
Map<String, Object> ret = new HashMap<>(4);
//TCC name
String actionName = businessAction.name();
//構造業務上下文
BusinessActionContext actionContext = new BusinessActionContext();
actionContext.setXid(xid);
//設置name
actionContext.setActionName(actionName);
//RM分支註冊,服務端響應BranchId
String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);
actionContext.setBranchId(branchId);
//業務上下文BusinessActionContext 添加到方法參數中
//上文使用中--tccActionOne.prepare(null, 1);參數需要設置爲nul
Class<?>[] types = method.getParameterTypes();
int argIndex = 0;
for (Class<?> cls : types) {
if (cls.getName().equals(BusinessActionContext.class.getName())) {
arguments[argIndex] = actionContext;
break;
}
argIndex++;
}
//返回最終參數
ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);
//執行業務方法
ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());
return ret;
}
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
BusinessActionContext actionContext) {
String actionName = actionContext.getActionName();
String xid = actionContext.getXid();
//獲取擴展參數 @BusinessActionContextParameter註解參數可以在Context中獲取
Map<String, Object> context = fetchActionRequestContext(method, arguments);
context.put(Constants.ACTION_START_TIME, System.currentTimeMillis());
//初始化業務上下文
initBusinessContext(context, method, businessAction);
//Init running environment context
initFrameworkContext(context);
actionContext.setActionContext(context);
//應用數據
Map<String, Object> applicationContext = new HashMap<>(4);
applicationContext.put(Constants.TCC_ACTION_CONTEXT, context);
String applicationContextStr = JSON.toJSONString(applicationContext);
try {
//RM向TC註冊分支,TC返回分支Id
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
applicationContextStr, null);
return String.valueOf(branchId);
} catch (Throwable t) {
String msg = String.format("TCC branch Register error, xid: %s", xid);
LOGGER.error(msg, t);
throw new FrameworkException(t, msg);
}
}
TCC分支事務執行會先執行切面攔截器invoke方法
,其實際過程是通過切面處理器ActionInterceptorHandler.proceed
方法去完成分支註冊方法執行。而分支註冊是默認RM實現DefaultResourceManager.branchRegister
完成。DefaultResourceManager
主要管理各類RM,而分支註冊是根據不同模式下的Rm去完成分支註冊。TCC模式下通過TCCResourceManager
完成分支註冊,其內部調用父類AbstractResourceManager.branchRegister
最終完成分支註冊。
DefaultResourceManager.branchRegister
public Long branchRegister(BranchType branchType, String resourceId,
String clientId, String xid, String applicationData, String lockKeys)
throws TransactionException {
//判斷分支類型 TCC / SAGA 去完成註冊
return getResourceManager(branchType).branchRegister(branchType, resourceId, clientId, xid, applicationData,
lockKeys);
}
AbstractResourceManager.branchRegister
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
...
BranchRegisterRequest request = new BranchRegisterRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
request.setBranchType(branchType);
request.setApplicationData(applicationData);
//向服務端發起分支事務註冊 ,服務端響應
BranchRegisterResponse response = (BranchRegisterResponse) RmRpcClient.getInstance().sendMsgWithResponse(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
return response.getBranchId();
...
}
服務端
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
String applicationData, String lockKeys) throws TransactionException {
//校驗全局Session
GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
return SessionHolder.lockAndExecute(globalSession, () -> {
globalSessionStatusCheck(globalSession);
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
//創建分支session
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
applicationData, lockKeys, clientId);
//鎖定當前session
branchSessionLock(globalSession, branchSession);
try {
//把當前分支事務添加到全局事務中
globalSession.addBranch(branchSession);
} catch (RuntimeException ex) {
//解除鎖定
branchSessionUnlock(branchSession);
...
}
//返回分支ID
return branchSession.getBranchId();
});
}
這裏就完成了方法的執行
事務回滾
方法執行出現異常,會向TC服務端發起全局回滾請求,TC 在收到回滾請求後通知各事務分支進行分支回滾並返回TC分支回滾狀態,事務回滾的方法與事務提交的邏輯一致
TransactionalTemplate.rollbackTransaction
->GlobalTransaction.rollback
->DefaultGlobalTrans.rollback
->DefaultTransactionManager.rollback
向服務器發送全局回滾請求
public GlobalStatus rollback(String xid) throws TransactionException {
//構造全局回滾請求
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
globalRollback.setXid(xid);
//發送請求 TC響應結果
GlobalRollbackResponse response = (GlobalRollbackResponse)syncCall(globalRollback);
return response.getGlobalStatus();
}
服務器DefaultCore.rollback
public GlobalStatus rollback(String xid) throws TransactionException {
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
if (globalSession == null) {
return GlobalStatus.Finished;
}
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
// 鎖定當前狀態
boolean shouldRollBack = SessionHolder.lockAndExecute(globalSession, () -> {
globalSession.close(); // 關閉全局session註冊
if (globalSession.getStatus() == GlobalStatus.Begin) {
globalSession.changeStatus(GlobalStatus.Rollbacking);
return true;
}
return false;
});
if (!shouldRollBack) {
return globalSession.getStatus();
}
doGlobalRollback(globalSession, false);
return globalSession.getStatus();
}
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
boolean success = true;
// 開啓回滾事件
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
globalSession.getTransactionName(), globalSession.getBeginTime(), null, globalSession.getStatus()));
//是否爲SAGA 使用SAGA模式回滾
if (globalSession.isSaga()) {
success = getCore(BranchType.SAGA).doGlobalRollback(globalSession, retrying);
} else {
//回滾各分支事務
for (BranchSession branchSession : globalSession.getReverseSortedBranches()) {
BranchStatus currentBranchStatus = branchSession.getStatus();
//一階段失敗不需要回滾,從全局事務中移除
if (currentBranchStatus == BranchStatus.PhaseOne_Failed) {
globalSession.removeBranch(branchSession);
continue;
}
try {
//分支回滾
BranchStatus branchStatus = branchRollback(globalSession, branchSession);
...
} catch (Exception ex) {
...
}
}
...
}
...
return success;
}
public BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
try {
//構造分支回滾請求
BranchRollbackRequest request = new BranchRollbackRequest();
request.setXid(branchSession.getXid());
request.setBranchId(branchSession.getBranchId());
request.setResourceId(branchSession.getResourceId());
request.setApplicationData(branchSession.getApplicationData());
request.setBranchType(branchSession.getBranchType());
//向客戶端TM發送請求回滾分支
return branchRollbackSend(request, globalSession, branchSession);
} catch (IOException | TimeoutException e) {
...
}
}
TCC RM執行分支回滾TCCResourceManager.branchRollback
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
...
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
//通過BusinessActionContext註解配置的rollback方法進行分支回滾
Object ret = rollbackMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource rollback result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
...
}
到這裏也就完成了對全局事務與分支事務的回滾操作,事務提交操作與之一致,在分支提交時會調用預留的接口,從而完成分佈式事務操作。