事务回滚基本原理
TM发送全局事务请求到TC,TC发送branch rollback请求到Order,Account,Storage 三个业务分支,三个业务分支各自回滚本地事务。
上图四个业务代码在seata-samples/spring-boot-mybatis项目
TC在seata项目
Business
1.BusinessController#purchaseCommit 入口
GlobalTransactional注解拦截,调用下面invoke方法
2.GlobalTransactionalInterceptor#invoke
3.GlobalTransactionalInterceptor#handleGlobalTransaction
4.TransactionalTemplate#execute
中间的business.execute()执行的最终代码是下面红色框的业务代码,代码位置BusinessService#purchase
在Account那里故意抛出异常,所以上面的business.execute这一步会报错,进而执行completeTransactionAfterThrowing方法。
Account异常代码位置AccountService#debit
5.TransactionalTemplate#completeTransactionAfterThrowing
6.TransactionalTemplate#rollbackTransaction
7.DefaultGlobalTransaction#rollback
8.DefaultTransactionManager#rollback
生成全局rollback请求,设置xid,发送同步请求到TC。
下面看看TC服务端是怎么处理的
TC
9.AbstractRpcRemoting#channelRead
接收Business的Rollback消息
10.AbstractRpcRemotingServer#dispatch
11.DefaultServerMessageListenerImpl#onTrxMessage
12.DefaultCoordinator#onRequest
13.AbstractTCInboundHandler#handle
14.DefaultCoordinator#doGlobalRollback
15.DefaultCore#rollback
16.DefaultCore#doGlobalRollback
循环遍历Order,Storage,Account分支,给每个业务分支发送branchRollback请求,回滚本地事务。
17.AbstractCore#branchRollback
下面看看Account分支的处理逻辑,其他两个分支原理类似。
Account
18.RmMessageListener#onMessage
接收TC的BranchRollback消息
19.RmMessageListener#handleBranchRollback
20.AbstractRMHandler#handle
21.AbstractRMHandler#doBranchRollback
22.AbstractUndoLogManager#undo
下面是回滚的关键代码
public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException {
Connection conn = null;
ResultSet rs = null;
PreparedStatement selectPST = null;
boolean originalAutoCommit = true;
for (; ; ) {
try {
conn = dataSourceProxy.getPlainConnection();
// The entire undo process should run in a local transaction.
if (originalAutoCommit = conn.getAutoCommit()) {
conn.setAutoCommit(false);
}
// Find UNDO LOG
selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL);
selectPST.setLong(1, branchId);
selectPST.setString(2, xid);
rs = selectPST.executeQuery();
boolean exists = false;
while (rs.next()) {
exists = true;
// It is possible that the server repeatedly sends a rollback request to roll back
// the same branch transaction to multiple processes,
// ensuring that only the undo_log in the normal state is processed.
int state = rs.getInt(ClientTableColumnsName.UNDO_LOG_LOG_STATUS);
if (!canUndo(state)) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("xid {} branch {}, ignore {} undo_log", xid, branchId, state);
}
return;
}
String contextString = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT);
Map<String, String> context = parseContext(contextString);
byte[] rollbackInfo = getRollbackInfo(rs);
String serializer = context == null ? null : context.get(UndoLogConstants.SERIALIZER_KEY);
UndoLogParser parser = serializer == null ? UndoLogParserFactory.getInstance()
: UndoLogParserFactory.getInstance(serializer);
BranchUndoLog branchUndoLog = parser.decode(rollbackInfo);
try {
// put serializer name to local
setCurrentSerializer(parser.getName());
List<SQLUndoLog> sqlUndoLogs = branchUndoLog.getSqlUndoLogs();
if (sqlUndoLogs.size() > 1) {
Collections.reverse(sqlUndoLogs);
}
for (SQLUndoLog sqlUndoLog : sqlUndoLogs) {
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType()).getTableMeta(
conn, sqlUndoLog.getTableName(), dataSourceProxy.getResourceId());
sqlUndoLog.setTableMeta(tableMeta);
AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(
dataSourceProxy.getDbType(), sqlUndoLog);
undoExecutor.executeOn(conn);
}
} finally {
// remove serializer name
removeCurrentSerializer();
}
}
// If undo_log exists, it means that the branch transaction has completed the first phase,
// we can directly roll back and clean the undo_log
// Otherwise, it indicates that there is an exception in the branch transaction,
// causing undo_log not to be written to the database.
// For example, the business processing timeout, the global transaction is the initiator rolls back.
// To ensure data consistency, we can insert an undo_log with GlobalFinished state
// to prevent the local transaction of the first phase of other programs from being correctly submitted.
// See https://github.com/seata/seata/issues/489
if (exists) {
deleteUndoLog(xid, branchId, conn);
conn.commit();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("xid {} branch {}, undo_log deleted with {}", xid, branchId,
State.GlobalFinished.name());
}
} else {
insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn);
conn.commit();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("xid {} branch {}, undo_log added with {}", xid, branchId,
State.GlobalFinished.name());
}
}
return;
} catch (SQLIntegrityConstraintViolationException e) {
// Possible undo_log has been inserted into the database by other processes, retrying rollback undo_log
if (LOGGER.isInfoEnabled()) {
LOGGER.info("xid {} branch {}, undo_log inserted, retry rollback", xid, branchId);
}
} catch (Throwable e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException rollbackEx) {
LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
}
}
throw new BranchTransactionException(BranchRollbackFailed_Retriable, String
.format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,
branchId, e.getMessage()), e);
} finally {
try {
if (rs != null) {
rs.close();
}
if (selectPST != null) {
selectPST.close();
}
if (conn != null) {
if (originalAutoCommit) {
conn.setAutoCommit(true);
}
conn.close();
}
} catch (SQLException closeEx) {
LOGGER.warn("Failed to close JDBC resource while undo ... ", closeEx);
}
}
}
}
查询数据库表undo_log,获取rollback_info字段信息,rollback_info包括提交事务之前跟之后的表变动信息,根据这些信息恢复表的数据。
下图是rollback_info的示例样式,回滚依据beforeImage这部分。