seata TCC事務流程

版本:1.2.0

TCC是一種資源服務化的兩階段提交協議,用戶需要實現Try、Confirm/Cancel接口。通過一階段的Try操作決定執行Confirm(確認)或者Cancel(取消)操作。

image

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

image

  • 實現InitializingBeanafterPropertiesSet方法,會在類實例化時執行,主要是用於初始化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();

    }
  • 實現ApplicationContextAwaresetApplicationContext方法獲得spring容器
  • 實現DisposableBeandestroy方法,在容器關閉時進行操作
  • 繼承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);
        }
    }

通過上面的源碼可以看出有兩個核心攔截器,TccActionInterceptorTCC模式攔截器和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);
            ...
    }

到這裏也就完成了對全局事務與分支事務的回滾操作,事務提交操作與之一致,在分支提交時會調用預留的接口,從而完成分佈式事務操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章