前面我们分析过seataTM服务启动时对@@GlobalTransactional注解修饰的方法进行了代理,本文我们看一下seata全局事务是如何开启的
一、开始全局事务
1.1 TM发起全局事务请求
DefaultTransactionManager::begin入口方法,这个方法只是构建了GlobalBeginRequest并通过nettyrpc同步调用发送给seata-server,关于rpc这块后面开一个专题分析阿里对netty-rpc的封装
@Override
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();
}
1.2、TC处理请求并返回XID
入口:io.seata.server.coordinator.DefaultCore#begin
@Override
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();
// transaction start event
eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
session.getTransactionName(), session.getBeginTime(), null, session.getStatus()));
return session.getXid();
}
二、提交全局事务
2.1 TM入口方法:DefaultTransactionManager#commit构建GlobalCommitRequest请求并发送给seata-server
@Override
public GlobalStatus commit(String xid) throws TransactionException {
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setXid(xid);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
2.2 TC处理全局提交请求
@Override
public GlobalCommitResponse handle(GlobalCommitRequest request, final RpcContext rpcContext) {
GlobalCommitResponse response = new GlobalCommitResponse();
response.setGlobalStatus(GlobalStatus.Committing);
exceptionHandleTemplate(new AbstractCallback<GlobalCommitRequest, GlobalCommitResponse>() {
@Override
public void execute(GlobalCommitRequest request, GlobalCommitResponse response)
throws TransactionException {
try {
doGlobalCommit(request, response, rpcContext);// @1
} catch (StoreException e) {
throw new TransactionException(TransactionExceptionCode.FailedStore,
String.format("global commit request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()),
e);
}
}
@Override
public void onTransactionException(GlobalCommitRequest request, GlobalCommitResponse response,
TransactionException tex) {
super.onTransactionException(request, response, tex);
checkTransactionStatus(request, response);
}
@Override
public void onException(GlobalCommitRequest request, GlobalCommitResponse response, Exception rex) {
super.onException(request, response, rex);
checkTransactionStatus(request, response);
}
}, request, response);
return response;
}
代码@1 :真正执行提交的方法doGlobalCommit
@Override
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, () -> {
// Highlight: Firstly, close the session, then no more branch can be registered.
globalSession.closeAndClean(); // @1
if (globalSession.getStatus() == GlobalStatus.Begin) {
if (globalSession.canBeCommittedAsync()) { // @2
// @3 执行异步提交,因为TM发起了全局提交,所以只要异步删除undo_log即可
//io.seata.server.coordinator.DefaultCoordinator.handleAsyncCommitting 线程不断将AsyncCommitting状态的 通知RM可以提交事务
globalSession.asyncCommit(); // @3
return false;
} else {
// @4 执行同步提交,返回true 然后下面shouldCommit代码会执行同步提交
globalSession.changeStatus(GlobalStatus.Committing);
return true;
}
}
return false;
});
// XA/TCC模式
if (shouldCommit) {
boolean success = doGlobalCommit(globalSession, false);
if (success && !globalSession.getBranchSessions().isEmpty()) {
globalSession.asyncCommit();
return GlobalStatus.Committed;
} else {
return globalSession.getStatus();
}
} else {
return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();
}
}
代码@1:释放分支锁 执行SessionLifecycleListener.onClose()
代码@2: 判断是否可以异步提交(XA/TCC不支持) AT模式支持,必须所有分支事务都已执行了本地提交,可以提交
代码@3:执行异步提交,因TM发起了全局提交,由io.seata.server.coordinator.DefaultCoordinator.handleAsyncCommitting 线程不断获取AsyncCommitting状态的session 通知RM可以提交事务
public void asyncCommit() throws TransactionException {
this.addSessionLifecycleListener(SessionHolder.getAsyncCommittingSessionManager()); //@1
SessionHolder.getAsyncCommittingSessionManager().addGlobalSession(this); //@2
this.changeStatus(GlobalStatus.AsyncCommitting); //@3
}
代码@1: 添加监听器
代码@2: 提交到异步管理器
代码@3: 状态设为【提交中】,然后DefaultCoordinator.handleAsyncCommitting会处理
Server类的main方法构造DefaultCoordinator后会启动很多定时任务,包括异步提交
asyncCommitting.scheduleAtFixedRate(() -> {
try {
handleAsyncCommitting();
} catch (Exception e) {
LOGGER.info("Exception async committing ... ", e);
}
}, 0, ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
protected void handleAsyncCommitting() {
Collection<GlobalSession> asyncCommittingSessions = SessionHolder.getAsyncCommittingSessionManager()
.allSessions();
if (CollectionUtils.isEmpty(asyncCommittingSessions)) {
return;
}
for (GlobalSession asyncCommittingSession : asyncCommittingSessions) {
try {
// Instruction reordering in DefaultCore#asyncCommit may cause this situation
if (GlobalStatus.AsyncCommitting != asyncCommittingSession.getStatus()) { // @1
continue;
}
asyncCommittingSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
core.doGlobalCommit(asyncCommittingSession, true); // @2
} catch (TransactionException ex) {
LOGGER.error("Failed to async committing [{}] {} {}", asyncCommittingSession.getXid(), ex.getCode(), ex.getMessage(), ex);
}
}
}
上面的代码还是比较通俗易懂的
代码@1: 只处理AsyncCommitting中的GlobalSession全局会话
代码@2:交给DefaultCore#doGlobalCommit通知每个session,在这个方法中会遍历该全局会话的每个分支会话,交给io.seata.server.coordinator.AbstractCore#branchCommit进行发送rpc通知
2.3 RM执行二阶段提交
如上图,RM接收到二阶段提交请求后,只需要做一件事:删除undolog
@Override
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
if (!ASYNC_COMMIT_BUFFER.offer(new Phase2Context(branchType, xid, branchId, resourceId, applicationData))) { //@1
LOGGER.warn("Async commit buffer is FULL. Rejected branch [{}/{}] will be handled by housekeeping later.", branchId, xid);
}
return BranchStatus.PhaseTwo_Committed;
}
代码@1: 这里采用生产者消费者模型,将请求offer到队列中,进行异步处理(有个定时任务) io.seata.rm.datasource.AsyncWorker#doBranchCommits因为这个方法太长,这里指贴核心部分
private void doBranchCommits() {
if (ASYNC_COMMIT_BUFFER.isEmpty()) {
return;
}
Map<String, List<Phase2Context>> mappedContexts = new HashMap<>(DEFAULT_RESOURCE_SIZE);
while (!ASYNC_COMMIT_BUFFER.isEmpty()) { //@1
Phase2Context commitContext = ASYNC_COMMIT_BUFFER.poll();
List<Phase2Context> contextsGroupedByResourceId = mappedContexts.computeIfAbsent(commitContext.resourceId, k -> new ArrayList<>());
contextsGroupedByResourceId.add(commitContext);
}
for (Map.Entry<String, List<Phase2Context>> entry : mappedContexts.entrySet()) { //@2
List<Phase2Context> contextsGroupedByResourceId = entry.getValue();
Set<String> xids = new LinkedHashSet<>(UNDOLOG_DELETE_LIMIT_SIZE);
Set<Long> branchIds = new LinkedHashSet<>(UNDOLOG_DELETE_LIMIT_SIZE);
for (Phase2Context commitContext : contextsGroupedByResourceId) {
xids.add(commitContext.xid);
branchIds.add(commitContext.branchId);
int maxSize = Math.max(xids.size(), branchIds.size());
if (maxSize == UNDOLOG_DELETE_LIMIT_SIZE) {
UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).batchDeleteUndoLog(xids, branchIds, conn); //@3
}
}
}
代码@1: 取出队列所有数据,按resourceId即数据库分组(全量取可以用drainTo替代)
代码@2: 遍历分组后的数据
代码@3 只需要删除undoLog即可
到这里二阶段提交就完成了