铿然架构 | 作者 / 铿然一叶 这是 铿然架构 的第 106 篇原创文章
1. 目的
对于如下支持全局事务的微服务调用场景:
通过本章能回答如下问题:
● 何时触发全局事务开启?
● 何时注册分支事务?
● 全局/分支事务commit在什么时机和如何触发?
● 全局/分支事务rollback在什么时机和如何触发?
● 事务的关键处理逻辑。
2. 源码分析
2.1 全局事务开启
2.1.1 客户端
客户端类结构如下:
GlobalTransactionalInterceptor类拦截有GlobalTransactional注解的业务方法发起全局事务:
GlobalTransactionalInterceptor.java
java
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);
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
final GlobalTransactional globalTransactionalAnnotation =
getAnnotation(method, targetClass, GlobalTransactional.class);
final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
if (!localDisable) {
// 业务方法上有GlobalTransactional注册则开启全局事务
if (globalTransactionalAnnotation != null) {
return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
} else if (globalLockAnnotation != null) {
return handleGlobalLock(methodInvocation, globalLockAnnotation);
}
}
}
return methodInvocation.proceed();
}
核心的全局事务开启逻辑在TransactionalTemplate中,会根据全局事务注册的事务传播级别做不同处理:
TransactionalTemplate.java
java
public Object execute(TransactionalExecutor business) throws Throwable {
// 1. Get transactionInfo
TransactionInfo txInfo = business.getTransactionInfo();
if (txInfo == null) {
throw new ShouldNeverHappenException("transactionInfo does not exist");
}
// 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 Handle the transaction propagation.
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
case NOT_SUPPORTED:
// If transaction is existing, suspend it.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
}
// Execute without transaction and return.
return business.execute();
case REQUIRES_NEW:
// If transaction is existing, suspend it, and then begin new transaction.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
tx = GlobalTransactionContext.createNew();
}
// Continue and execute with new transaction
break;
case SUPPORTS:
// If transaction is not existing, execute without transaction.
if (notExistingTransaction(tx)) {
return business.execute();
}
// Continue and execute with new transaction
break;
case REQUIRED:
// If current transaction is existing, execute with current transaction,
// else continue and execute with new transaction.
break;
case NEVER:
// If transaction is existing, throw exception.
if (existingTransaction(tx)) {
throw new TransactionException(
String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
, tx.getXid()));
} else {
// Execute without transaction and return.
return business.execute();
}
case MANDATORY:
// If transaction is not existing, throw exception.
if (notExistingTransaction(tx)) {
throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
}
// Continue and execute with current transaction.
break;
default:
throw new TransactionException("Not Supported Propagation:" + propagation);
}
// 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
if (tx == null) {
tx = GlobalTransactionContext.createNew();
}
// set current tx config to holder
GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
try {
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
// else do nothing. Of course, the hooks will still be triggered.
beginTransaction(txInfo, tx);
Object rs;
try {
// Do Your Business
rs = business.execute();
// 捕捉根异常,所有异常都能捕捉到
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. everything is fine, commit.
commitTransaction(tx);
return rs;
} finally {
//5. clear
resumeGlobalLockConfig(previousConfig);
triggerAfterCompletion();
cleanUp();
}
} finally {
// If the transaction is suspended, resume it.
if (suspendedResourcesHolder != null) {
tx.resume(suspendedResourcesHolder);
}
}
}
经过一系列处理后发起全局事务请求:
DefaultTransactionManager.java
java
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
全局事务开启后DefaultGlobalTransaction的属性如下:
序号 | 类型 |
---|---|
status | GlobalStatus.Begin |
xid | TC返回 |
role | GlobalTransactionRole.Launcher |
同时RootContext绑定了xid,后面使用时可以从RootContext获取。
2.1.2 服务端处理
服务端核心类如下:
核心逻辑在DefaultCore中,参考注释说明:
DefaultCore.java
java
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
timeout);
MDC.put(RootContext.MDC_KEY_XID, session.getXid());
// 添加session生命周期监听器
session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
// 激活session,改变session状态,同时触发session生命周期监听器的onBegin方法,持久化session
session.begin();
// 这里通过异步事件通知,调用MetricsSubscriber的recordGlobalTransactionEventForMetrics方法
eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
session.getTransactionName(), applicationId, transactionServiceGroup, session.getBeginTime(), null, session.getStatus()));
return session.getXid();
}
开启全局事务后GlobalSession的属性如下:
序号 | 类型 |
---|---|
transactionId | UUIDGenerator.generateUUID() |
applicationId | 应用标识,传入 |
transactionServiceGroup | 事务分组,传入 |
transactionName | 全局事务名称,传入 |
timeout | 全局事务超时时间 |
status | GlobalStatus.Begin |
beginTime | System.currentTimeMillis() |
active | true |
xid | XID.generateXID(transactionId) |
2.2 分支事务注册
2.2.1 客户端
核心类结构如下:
2.2.1.1 AT模式注册
在做commit时,如果是全局事务则发起分支注册。
最终在ConnectionProxy.java的register方法内:
java
private void register() throws TransactionException {
if (!context.hasUndoLog() || !context.hasLockKey()) {
return;
}
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(),
null, context.getXid(), null, context.buildLockKeys());
context.setBranchId(branchId);
}
注册参数:
序号 | 类型 |
---|---|
BranchType | BranchType.AT |
resourceId | 资源标识,前面文章中提到过获取逻辑 |
clientId | null |
xid | xid |
applicationData | null |
lockKeys | 根据规则获取 |
2.2.1.2 XA模式注册
在设置是否自动提交时做分支注册,只有非自动提交模式下才会注册。
ConnectionProxyXA.java的setAutoCommit方法:
java
branchId = DefaultResourceManager.get().branchRegister(BranchType.XA, resource.getResourceId(), null, xid, null,
null);
注册参数:
序号 | 类型 |
---|---|
BranchType | BranchType.XA |
resourceId | 资源标识,前面文章中提到过获取逻辑 |
clientId | null |
xid | xid |
applicationData | null |
lockKeys | null |
2.2.1.3 TCC模式注册
TccActionInterceptor拦截了有TwoPhaseBusinessAction注解的业务方法,在pre方法被调用时触发分支注册请求,最终注册调用点在如下方法。
ActionInterceptorHandler.java的doTccActionLogStore方法内:
java
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
applicationContextStr, null);
注册参数:
序号 | 类型 |
---|---|
BranchType | BranchType.TCC |
resourceId | TwoPhaseBusinessAction注解的name属性值 |
clientId | null |
xid | xid |
applicationData | 应用上下文数据,可参考TCC核心类和处理逻辑那篇文章 |
lockKeys | null |
2.2.2 服务端
核心类结构如下:
分支注册的核心逻辑入口在AbstractCore:
AbstractCore.java
java
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
String applicationData, String lockKeys) throws TransactionException {
// 根据xid获取GlobalSession,之前发起全局事务时做了持久化
GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
return SessionHolder.lockAndExecute(globalSession, () -> {
// 检查GlobalSession状态,包括active属性是否为true,status是否为GlobalStatus.Begin
globalSessionStatusCheck(globalSession);
// 添加生命周期监听器,这里是SessionManager接口的子类,配置的是哪类session管理类型,就是哪个,例如DataBaseSessionManager、RedisSessionManager、FileSessionManager
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
// 创建branchSession
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
applicationData, lockKeys, clientId);
MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
// branchSession加锁,只有AT模式下才会加锁,具体查看BranchSession的lock方法,会通过LockerManagerFactory获取到LockManager接口的子类来加锁
branchSessionLock(globalSession, branchSession);
try {
// globalSession添加branchSession,触发生命周期监听器的onAddBranch方法,branchSession的status设置为BranchStatus.Registered
globalSession.addBranch(branchSession);
} catch (RuntimeException ex) {
// branchSession解锁,具体查看BranchSession的unlock方法,会通过LockerManagerFactory获取到LockManager接口的子类来解锁
branchSessionUnlock(branchSession);
throw new BranchTransactionException(FailedToAddBranch, String
.format("Failed to store branch xid = %s branchId = %s", globalSession.getXid(),
branchSession.getBranchId()), ex);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Register branch successfully, xid = {}, branchId = {}, resourceId = {} ,lockKeys = {}",
globalSession.getXid(), branchSession.getBranchId(), resourceId, lockKeys);
}
// 返回给客户端
return branchSession.getBranchId();
});
}
注册后branchSession的信息如下:
序号 | 类型 |
---|---|
xid | 来自globalSession |
transactionId | 来自globalSession |
branchId | UUIDGenerator.generateUUID() |
branchType | 事务分支类型,如XA,AT |
resourceId | 资源标识 |
lockKeys | 加锁key |
clientId | 客户端标识 |
applicationData | 应用数据 |
status | BranchStatus.Registered |
2.4 全局事务commit
2.4.1 客户端
核心类结构如下:
2.4.1.1 全局事务提交起点
全局事务提交时在有注解的业务方法执行之后提交。分为两个场景:
1.业务方法执行成功,没有抛出异常
2.业务方法执行时抛出异常,此时会根据业务方法上的GlobalTransactional注解配置的异常处理规则决定是回退还是提交
成功时调用:
TransactionalTemplate.java
java
private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeCommit();
tx.commit();
triggerAfterCommit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
}
异常时调用,如果规则不允许提交则抛出异常,后续通过异常触发rollback:
TransactionalTemplate.java
java
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
//roll back txInfo.rollbackOn方法将抛出的异常和注解GlobalTransactional上配置的异常做比较
if (txInfo != null && txInfo.rollbackOn(originalException)) {
try {
rollbackTransaction(tx, originalException);
} catch (TransactionException txe) {
// Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, originalException);
}
} else {
// 如果配置的异常处理规则允许提交事务则继续提交
commitTransaction(tx);
}
}
2.4.1.2 全局事务提交请求
请求中只需要xid.
DefaultTransactionManager.java
java
public GlobalStatus commit(String xid) throws TransactionException {
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setXid(xid);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
2.4.2 服务端
核心类结构如下:
核心逻辑入口:
DefaultCore.java
java
public GlobalStatus commit(String xid) throws TransactionException {
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
if (globalSession == null) {
return GlobalStatus.Finished;
}
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
// just lock changeStatus
boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {
// 关闭session,设置active为fasle,清理掉branchSession,释放锁
globalSession.closeAndClean();
if (globalSession.getStatus() == GlobalStatus.Begin) {
if (globalSession.canBeCommittedAsync()) {
globalSession.asyncCommit();
return false;
} else {
globalSession.changeStatus(GlobalStatus.Committing);
return true;
}
}
return false;
});
if (shouldCommit) {
boolean success = doGlobalCommit(globalSession, false);
//If successful and all remaining branches can be committed asynchronously, do async commit.
if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {
globalSession.asyncCommit();
return GlobalStatus.Committed;
} else {
return globalSession.getStatus();
}
} else {
return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();
}
}
具体逻辑,对GlobalSession处理,同时触发分支事务提交:
java
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
boolean success = true;
// start committing event
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
globalSession.getTransactionName(), globalSession.getApplicationId(), globalSession.getTransactionServiceGroup(),
globalSession.getBeginTime(), null, globalSession.getStatus()));
// SAGA模式,直接返回true
if (globalSession.isSaga()) {
success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying);
} else {
Boolean result = SessionHelper.forEach(globalSession.getSortedBranches(), branchSession -> {
// if not retrying, skip the canBeCommittedAsync branches
if (!retrying && branchSession.canBeCommittedAsync()) {
return CONTINUE;
}
BranchStatus currentStatus = branchSession.getStatus();
if (currentStatus == BranchStatus.PhaseOne_Failed) {
globalSession.removeBranch(branchSession);
return CONTINUE;
}
try {
// 分支事务提交
BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
switch (branchStatus) {
case PhaseTwo_Committed:
globalSession.removeBranch(branchSession);
return CONTINUE;
case PhaseTwo_CommitFailed_Unretryable:
if (globalSession.canBeCommittedAsync()) {
LOGGER.error(
"Committing branch transaction[{}], status: PhaseTwo_CommitFailed_Unretryable, please check the business log.", branchSession.getBranchId());
return CONTINUE;
} else {
SessionHelper.endCommitFailed(globalSession);
LOGGER.error("Committing global transaction[{}] finally failed, caused by branch transaction[{}] commit failed.", globalSession.getXid(), branchSession.getBranchId());
return false;
}
default:
if (!retrying) {
globalSession.queueToRetryCommit();
return false;
}
if (globalSession.canBeCommittedAsync()) {
LOGGER.error("Committing branch transaction[{}], status:{} and will retry later",
branchSession.getBranchId(), branchStatus);
return CONTINUE;
} else {
LOGGER.error(
"Committing global transaction[{}] failed, caused by branch transaction[{}] commit failed, will retry later.", globalSession.getXid(), branchSession.getBranchId());
return false;
}
}
} catch (Exception ex) {
StackTraceLogger.error(LOGGER, ex, "Committing branch transaction exception: {}",
new String[] {branchSession.toString()});
if (!retrying) {
globalSession.queueToRetryCommit();
throw new TransactionException(ex);
}
}
return CONTINUE;
});
// Return if the result is not null
if (result != null) {
return result;
}
//If has branch and not all remaining branches can be committed asynchronously,
//do print log and return false
if (globalSession.hasBranch() && !globalSession.canBeCommittedAsync()) {
LOGGER.info("Committing global transaction is NOT done, xid = {}.", globalSession.getXid());
return false;
}
}
//If success and there is no branch, end the global transaction.
if (success && globalSession.getBranchSessions().isEmpty()) {
// 设置session状态为GlobalStatus.Committed,释放全局session锁,调用生命周期监听器的onEnd方法
SessionHelper.endCommitted(globalSession);
// committed event
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
globalSession.getTransactionName(), globalSession.getApplicationId(), globalSession.getTransactionServiceGroup(),
globalSession.getBeginTime(), System.currentTimeMillis(), globalSession.getStatus()));
LOGGER.info("Committing global transaction is successfully done, xid = {}.", globalSession.getXid());
}
return success;
}
2.5 分支事务提交
2.5.1 服务端
前面已经提及分支事务提交是在全局事务提交时触发,触发代码如下:
DefaultCore.java
java
BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
处理代码如下,构造分支提交请求发送给RM:
AbstractCore.java
java
public BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
try {
BranchCommitRequest request = new BranchCommitRequest();
request.setXid(branchSession.getXid());
request.setBranchId(branchSession.getBranchId());
request.setResourceId(branchSession.getResourceId());
request.setApplicationData(branchSession.getApplicationData());
request.setBranchType(branchSession.getBranchType());
return branchCommitSend(request, globalSession, branchSession);
} catch (IOException | TimeoutException e) {
throw new BranchTransactionException(FailedToSendBranchCommitRequest,
String.format("Send branch commit failed, xid = %s branchId = %s", branchSession.getXid(),
branchSession.getBranchId()), e);
}
}
2.5.2 客户端
客户端类结构:
2.5.2.1 AT模式
DataSourceManager发起,核心逻辑是删除undo日志,主要代码如下:
AsyncWorker.java
java
private void dealWithGroupedContexts(String resourceId, List<Phase2Context> contexts) {
DataSourceProxy dataSourceProxy = dataSourceManager.get(resourceId);
if (dataSourceProxy == null) {
LOGGER.warn("Failed to find resource for {}", resourceId);
return;
}
Connection conn;
try {
conn = dataSourceProxy.getPlainConnection();
} catch (SQLException sqle) {
LOGGER.error("Failed to get connection for async committing on {}", resourceId, sqle);
return;
}
UndoLogManager undoLogManager = UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType());
// split contexts into several lists, with each list contain no more element than limit size
List<List<Phase2Context>> splitByLimit = Lists.partition(contexts, UNDOLOG_DELETE_LIMIT_SIZE);
splitByLimit.forEach(partition -> deleteUndoLog(conn, undoLogManager, partition));
}
2.5.2.2 XA模式
ResourceManagerXA发起,核心处理代码如下:
ResourceManagerXA.java
java
private BranchStatus finishBranch(boolean committed, BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
XAXid xaBranchXid = XAXidBuilder.build(xid, branchId);
Resource resource = dataSourceCache.get(resourceId);
if (resource instanceof AbstractDataSourceProxyXA) {
try (ConnectionProxyXA connectionProxyXA = ((AbstractDataSourceProxyXA)resource).getConnectionForXAFinish(xaBranchXid)) {
if (committed) {
connectionProxyXA.xaCommit(xid, branchId, applicationData);
LOGGER.info(xaBranchXid + " was committed.");
return BranchStatus.PhaseTwo_Committed;
} else {
connectionProxyXA.xaRollback(xid, branchId, applicationData);
LOGGER.info(xaBranchXid + " was rollbacked");
return BranchStatus.PhaseTwo_Rollbacked;
}
} catch (XAException | SQLException sqle) {
if (sqle instanceof XAException) {
if (((XAException)sqle).errorCode == XAException.XAER_NOTA) {
if (committed) {
return BranchStatus.PhaseTwo_Committed;
} else {
return BranchStatus.PhaseTwo_Rollbacked;
}
}
}
if (committed) {
LOGGER.info(xaBranchXid + " commit failed since " + sqle.getMessage(), sqle);
// FIXME: case of PhaseTwo_CommitFailed_Unretryable
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
} else {
LOGGER.info(xaBranchXid + " rollback failed since " + sqle.getMessage(), sqle);
// FIXME: case of PhaseTwo_RollbackFailed_Unretryable
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
} else {
LOGGER.error("Unknown Resource for XA resource " + resourceId + " " + resource);
if (committed) {
return BranchStatus.PhaseTwo_CommitFailed_Unretryable;
} else {
return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
}
}
}
2.5.2.3 TCC模式
由TCCResourceManager发起,核心逻辑是找到业务方法TwoPhaseBusinessAction注解commitMethod属性指定的方法并调用:
TCCResourceManager.java
java
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
// 获取TwoPhaseBusinessAction注解commitMethod属性指定的方法并调用
Method commitMethod = tccResource.getCommitMethod();
if (targetTCCBean == null || commitMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = commitMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
}
}
2.6 全局事务回滚
2.6.1 客户端代码
类结构可以参考全局事务提交。
全局事务回滚在全结局事务注解的业务方法调用发生异常时触发:
TransactionalTemplate.java
java
try {
// Do Your Business
rs = business.execute();
// 捕捉根异常,所有异常都能捕捉到
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
//roll back txInfo.rollbackOn方法将抛出的异常和注解GlobalTransactional上配置的异常做比较
if (txInfo != null && txInfo.rollbackOn(originalException)) {
try {
rollbackTransaction(tx, originalException);
} catch (TransactionException txe) {
// Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, originalException);
}
} else {
// 如果配置的异常处理规则允许提交事务则继续提交
commitTransaction(tx);
}
}
private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException {
triggerBeforeRollback();
tx.rollback();
triggerAfterRollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus())
? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException);
}
向服务端发送请求,只需要传递xid:
DefaultTransactionManager.java
java
public GlobalStatus rollback(String xid) throws TransactionException {
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
globalRollback.setXid(xid);
GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback);
return response.getGlobalStatus();
}
2.6.2 服务端代码
类结构参考全局事务提交类结构。
核心逻辑在DefaultCore,同时触发分支事务rollback:
DefaultCore.java
java
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
boolean success = true;
// start rollback event
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(),
GlobalTransactionEvent.ROLE_TC, globalSession.getTransactionName(),
globalSession.getApplicationId(),
globalSession.getTransactionServiceGroup(), globalSession.getBeginTime(),
null, globalSession.getStatus()));
if (globalSession.isSaga()) {
success = getCore(BranchType.SAGA).doGlobalRollback(globalSession, retrying);
} else {
Boolean result = SessionHelper.forEach(globalSession.getReverseSortedBranches(), branchSession -> {
BranchStatus currentBranchStatus = branchSession.getStatus();
if (currentBranchStatus == BranchStatus.PhaseOne_Failed) {
globalSession.removeBranch(branchSession);
return CONTINUE;
}
try {
BranchStatus branchStatus = branchRollback(globalSession, branchSession);
switch (branchStatus) {
case PhaseTwo_Rollbacked:
globalSession.removeBranch(branchSession);
LOGGER.info("Rollback branch transaction successfully, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
return CONTINUE;
case PhaseTwo_RollbackFailed_Unretryable:
SessionHelper.endRollbackFailed(globalSession);
LOGGER.info("Rollback branch transaction fail and stop retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
return false;
default:
LOGGER.info("Rollback branch transaction fail and will retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
if (!retrying) {
globalSession.queueToRetryRollback();
}
return false;
}
} catch (Exception ex) {
StackTraceLogger.error(LOGGER, ex,
"Rollback branch transaction exception, xid = {} branchId = {} exception = {}",
new String[] {globalSession.getXid(), String.valueOf(branchSession.getBranchId()), ex.getMessage()});
if (!retrying) {
globalSession.queueToRetryRollback();
}
throw new TransactionException(ex);
}
});
// Return if the result is not null
if (result != null) {
return result;
}
// In db mode, there is a problem of inconsistent data in multiple copies, resulting in new branch
// transaction registration when rolling back.
// 1. New branch transaction and rollback branch transaction have no data association
// 2. New branch transaction has data association with rollback branch transaction
// The second query can solve the first problem, and if it is the second problem, it may cause a rollback
// failure due to data changes.
GlobalSession globalSessionTwice = SessionHolder.findGlobalSession(globalSession.getXid());
if (globalSessionTwice != null && globalSessionTwice.hasBranch()) {
LOGGER.info("Rollbacking global transaction is NOT done, xid = {}.", globalSession.getXid());
return false;
}
}
if (success) {
SessionHelper.endRollbacked(globalSession);
// rollbacked event
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(),
GlobalTransactionEvent.ROLE_TC, globalSession.getTransactionName(),
globalSession.getApplicationId(),
globalSession.getTransactionServiceGroup(),
globalSession.getBeginTime(), System.currentTimeMillis(),
globalSession.getStatus()));
LOGGER.info("Rollback global transaction successfully, xid = {}.", globalSession.getXid());
}
return success;
}
2.7 分支事务rollback
2.7.1 服务端
前面已经提及分支事务提交时在全局事务回滚时触发,触发代码如下:
DefaultCore.java
java
BranchStatus branchStatus = branchRollback(globalSession, branchSession);
处理代码如下,构造分支提交请求发送给RM:
AbstractCore.java
java
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());
return branchRollbackSend(request, globalSession, branchSession);
} catch (IOException | TimeoutException e) {
throw new BranchTransactionException(FailedToSendBranchRollbackRequest,
String.format("Send branch rollback failed, xid = %s branchId = %s",
branchSession.getXid(), branchSession.getBranchId()), e);
}
}
2.7.2 客户端
类结构参考分支事务提交。
2.7.2.1 AT模式
DataSourceManager发起,核心逻辑是根据undo日志执行undo操作,主要代码如下:
DataSourceManager.java
java
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
DataSourceProxy dataSourceProxy = get(resourceId);
if (dataSourceProxy == null) {
throw new ShouldNeverHappenException();
}
try {
UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).undo(dataSourceProxy, xid, branchId);
} catch (TransactionException te) {
StackTraceLogger.info(LOGGER, te,
"branchRollback failed. branchType:[{}], xid:[{}], branchId:[{}], resourceId:[{}], applicationData:[{}]. reason:[{}]",
new Object[]{branchType, xid, branchId, resourceId, applicationData, te.getMessage()});
if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) {
return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
} else {
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
return BranchStatus.PhaseTwo_Rollbacked;
}
2.7.2.2 XA模式
ResourceManagerXA发起,核心处理代码如下:
ResourceManagerXA.java
java
private BranchStatus finishBranch(boolean committed, BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
XAXid xaBranchXid = XAXidBuilder.build(xid, branchId);
Resource resource = dataSourceCache.get(resourceId);
if (resource instanceof AbstractDataSourceProxyXA) {
try (ConnectionProxyXA connectionProxyXA = ((AbstractDataSourceProxyXA)resource).getConnectionForXAFinish(xaBranchXid)) {
if (committed) {
connectionProxyXA.xaCommit(xid, branchId, applicationData);
LOGGER.info(xaBranchXid + " was committed.");
return BranchStatus.PhaseTwo_Committed;
} else {
connectionProxyXA.xaRollback(xid, branchId, applicationData);
LOGGER.info(xaBranchXid + " was rollbacked");
return BranchStatus.PhaseTwo_Rollbacked;
}
} catch (XAException | SQLException sqle) {
if (sqle instanceof XAException) {
if (((XAException)sqle).errorCode == XAException.XAER_NOTA) {
if (committed) {
return BranchStatus.PhaseTwo_Committed;
} else {
return BranchStatus.PhaseTwo_Rollbacked;
}
}
}
if (committed) {
LOGGER.info(xaBranchXid + " commit failed since " + sqle.getMessage(), sqle);
// FIXME: case of PhaseTwo_CommitFailed_Unretryable
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
} else {
LOGGER.info(xaBranchXid + " rollback failed since " + sqle.getMessage(), sqle);
// FIXME: case of PhaseTwo_RollbackFailed_Unretryable
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
} else {
LOGGER.error("Unknown Resource for XA resource " + resourceId + " " + resource);
if (committed) {
return BranchStatus.PhaseTwo_CommitFailed_Unretryable;
} else {
return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
}
}
}
2.7.2.3 TCC模式
由TCCResourceManager发起,核心逻辑是找到业务方法TwoPhaseBusinessAction注解rollbackMethod属性指定的方法并调用:
TCCResourceManager.java
java
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
Method rollbackMethod = tccResource.getRollbackMethod();
if (targetTCCBean == null || rollbackMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = rollbackMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource rollback result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Rollbacked : BranchStatus.PhaseTwo_RollbackFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("rollback TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
3. 总结
最后,全局事务涉及的生命周期和触发时机如下:
事务生命周期 | 触发时机 |
---|---|
全局事务开启 | 有GlobalTransactional注解的业务方法调用时 |
分支事务注册 | 不同模式不同。AT在事务提交时;XA在调用setAutoCommit(false)方法时;TCC在调用prepare方法时。 |
全局事务commit | 在有GlobalTransactional注解的业务方法调用之后提交 |
分支事务commit | 全局事务提交时触发,先完成分支事务commit |
全局事务rollback | 在有GlobalTransactional注解的业务方法调用发生异常时触发,可以配置规则是否忽略异常 |
分支事务rollback | 在全局事务rollback时触发,先完成分支事务rollback |
其他阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃\