Seata-AT全局事务的开启、提交

1,366 阅读3分钟

前面我们分析过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即可

到这里二阶段提交就完成了