tcc分散式事務框架原始碼解析系列(五)之專案實戰

yu199195發表於2017-10-13

接上一篇,我們已經分析了在整個消費的呼叫流程,現在只差發起真實的rpc遠端呼叫了,這篇文章,我們一起進入提供者的呼叫流程吧!

  • 我們發起 accountService.payment(accountDTO); 的呼叫,在提供方,我們可以看到其實現類為AccountServiceImpl:

/**
    * 扣款支付
    *
    * @param accountDTO 引數dto
    * @return true
    */
   @Override
   @Tcc(confirmMethod = "confirm", cancelMethod = "cancel")
   public boolean payment(AccountDTO accountDTO) {
       final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
       accountDO.setBalance(accountDO.getBalance().subtract(accountDTO.getAmount()));
       accountDO.setFreezeAmount(accountDO.getFreezeAmount().add(accountDTO.getAmount()));
       accountDO.setUpdateTime(new Date());
       final int update = accountMapper.update(accountDO);
       if (update != 1) {
           throw new TccRuntimeException("資金不足!");
       }
       return Boolean.TRUE;
   }

   public boolean confirm(AccountDTO accountDTO) {

       LOGGER.debug("============執行確認付款介面===============");

       final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
       accountDO.setFreezeAmount(accountDO.getFreezeAmount().subtract(accountDTO.getAmount()));
       accountDO.setUpdateTime(new Date());
       accountMapper.update(accountDO);
       return Boolean.TRUE;
   }


   public boolean cancel(AccountDTO accountDTO) {

       LOGGER.debug("============執行取消付款介面===============");
       final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
       accountDO.setBalance(accountDO.getBalance().add(accountDTO.getAmount()));
       accountDO.setFreezeAmount(accountDO.getFreezeAmount().subtract(accountDTO.getAmount()));
       accountDO.setUpdateTime(new Date());
       accountMapper.update(accountDO);
       return Boolean.TRUE;
   }複製程式碼
  • 我們發現它也有@Tcc註解,並且提供了confrim,cancel等真實的方法。通過前面一篇的分析,我們知道,他是springBean的一個實現類,同樣會走切面。

  • 經過 TccTransactionFactoryServiceImpl 的 factoryOf方法,我們可以知道他會返回 ProviderTccTransactionHandler

@Override
   public Class factoryOf(TccTransactionContext context) throws Throwable {

       //如果事務還沒開啟或者 tcc事務上下文是空, 那麼應該進入發起呼叫
       if (!tccTransactionManager.isBegin() && Objects.isNull(context)) {
           return StartTccTransactionHandler.class;
       } else if (tccTransactionManager.isBegin() && Objects.isNull(context)) {
           return ConsumeTccTransactionIHandler.class;
       } else if (Objects.nonNull(context)) {
           return ProviderTccTransactionHandler.class;
       }
       return ConsumeTccTransactionIHandler.class;
   }複製程式碼
  • 最終我們來到 ProviderTccTransactionHandler.handler 方法:
/**
    * 分散式事務提供者處理介面
    * 根據tcc事務上下文的狀態來執行相對應的方法
    *
    * @param point   point 切點
    * @param context context
    * @return Object
    * @throws Throwable 異常
    */
   @Override
   public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
       TccTransaction tccTransaction = null;
       try {
           switch (TccActionEnum.acquireByCode(context.getAction())) {
               case TRYING:
                   try {
                       //建立事務資訊
                       tccTransaction = tccTransactionManager.providerBegin(context);
                       //發起方法呼叫
                       return point.proceed();
                   } catch (Throwable throwable) {
                       tccTransactionManager.removeTccTransaction(tccTransaction);
                       throw throwable;

                   }
               case CONFIRMING:
                   //如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
                   final TccTransaction acquire = tccTransactionManager.acquire(context);
                   tccTransactionManager.confirm();
                   break;
               case CANCELING:
                   //如果是呼叫CANCELING 通過之前儲存的事務資訊 進行反射呼叫
                   tccTransactionManager.acquire(context);
                   tccTransactionManager.cancel();
                   break;
               default:
                   break;
           }
       } finally {
           tccTransactionManager.remove();
       }
       Method method = ((MethodSignature) (point.getSignature())).getMethod();
       return getDefaultValue(method.getReturnType());
   }複製程式碼
  • TccTransactionContext 就是通過rpc json序列化後傳過來的物件,此時我們知道是在try階段,所以我們進入try
try {
    //建立事務資訊
    tccTransaction = tccTransactionManager.providerBegin(context);
    //發起方法呼叫
    return point.proceed();
} catch (Throwable throwable) {
    tccTransactionManager.removeTccTransaction(tccTransaction);
    throw throwable;

}複製程式碼
  • 首先我們會建立提供者的事務資訊,並把他存起來,再把它存入threadlocal中,接著發起 point.proceed() 呼叫的時候,我們會進入
    TccCoordinatorMethodAspect,由於是在try階段最終會進入:
/**
    * 獲取呼叫介面的協調方法並封裝
    *
    * @param point 切點
    */
   private void registerParticipant(ProceedingJoinPoint point, String transId) throws NoSuchMethodException {

       MethodSignature signature = (MethodSignature) point.getSignature();
       Method method = signature.getMethod();

       Class<?> clazz = point.getTarget().getClass();

       Object[] args = point.getArgs();

       final Tcc tcc = method.getAnnotation(Tcc.class);

       //獲取協調方法
       String confirmMethodName = tcc.confirmMethod();

      /* if (StringUtils.isBlank(confirmMethodName)) {
           confirmMethodName = method.getName();
       }*/

       String cancelMethodName = tcc.cancelMethod();

      /* if (StringUtils.isBlank(cancelMethodName)) {
           cancelMethodName = method.getName();
       }
*/
       //設定模式
       final TccPatternEnum pattern = tcc.pattern();

       tccTransactionManager.getCurrentTransaction().setPattern(pattern.getCode());


       TccInvocation confirmInvocation = null;
       if (StringUtils.isNoneBlank(confirmMethodName)) {
           confirmInvocation = new TccInvocation(clazz,
                   confirmMethodName, method.getParameterTypes(), args);
       }

       TccInvocation cancelInvocation = null;
       if (StringUtils.isNoneBlank(cancelMethodName)) {
           cancelInvocation = new TccInvocation(clazz,
                   cancelMethodName,
                   method.getParameterTypes(), args);
       }


       //封裝呼叫點
       final Participant participant = new Participant(
               transId,
               confirmInvocation,
               cancelInvocation);

       tccTransactionManager.enlistParticipant(participant);

   }複製程式碼
  • 這裡獲取真實的confrim,cancel方法並存入當前的事務資訊中。然後發起真實的業務呼叫 ,即執行payment方法:

  @Override
  @Tcc(confirmMethod = "confirm", cancelMethod = "cancel")
  public boolean payment(AccountDTO accountDTO) {
      final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
      accountDO.setBalance(accountDO.getBalance().subtract(accountDTO.getAmount()));
      accountDO.setFreezeAmount(accountDO.getFreezeAmount().add(accountDTO.getAmount()));
      accountDO.setUpdateTime(new Date());
      final int update = accountMapper.update(accountDO);
      if (update != 1) {
          throw new TccRuntimeException("資金不足!");
      }
      return Boolean.TRUE;
  }

`複製程式碼
  • 當我們執行完該方法後,會返回,還記得我是在哪裡來執行這個方法的嗎?對,當然是切面,我們是在切面裡執行的,我們是在
    PaymentServiceImpl.makePayment 切面裡面執行的! 請要理解這一點,執行完後,我們發起了 inventoryService.decrease(inventoryDTO) 呼叫
    他的呼叫原理和上面一模一樣,只是在不同的模組裡面執行。當 makePayment 方法執行完後,我們該怎麼執行? 你還記得 StartTccTransactionHandler嗎,它可一直在那等呢。。
    我們再來回顧下他的程式碼:
@Override
   public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
       Object returnValue;
       try {
           tccTransactionManager.begin();
           try {
               //發起呼叫 執行try方法
               returnValue = point.proceed();

           } catch (Throwable throwable) {
               //異常執行cancel

               tccTransactionManager.cancel();

               throw throwable;
           }
           //try成功執行confirm confirm 失敗的話,那就只能走本地補償
           tccTransactionManager.confirm();
       } finally {
           tccTransactionManager.remove();
       }
       return returnValue;
   }複製程式碼
  • 說到底,我們走了這麼久,其實到這裡,我們才執行完 returnValue = point.proceed(); 這一句程式碼。

沒有異常

  • 我們會執行 tccTransactionManager.confirm(); 我們跟進去看程式碼:
/**
     * 呼叫confirm方法 這裡主要如果是發起者呼叫 這裡呼叫遠端的還是原來的方法,不過上下文設定了呼叫confirm
     * 那麼遠端的服務則會呼叫confirm方法。。
     */
    void confirm() throws TccRuntimeException {

        LogUtil.debug(LOGGER, () -> "開始執行tcc confirm 方法!start");

        final TccTransaction currentTransaction = getCurrentTransaction();

        if (Objects.isNull(currentTransaction)) {
            return;
        }

        currentTransaction.setStatus(TccActionEnum.CONFIRMING.getCode());

        coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.UPDATE, currentTransaction));

        final List<Participant> participants = currentTransaction.getParticipants();
        List<Participant> participantList = Lists.newArrayListWithCapacity(participants.size());
        boolean success = true;
        Participant fail = null;
        if (CollectionUtils.isNotEmpty(participants)) {
            for (Participant participant : participants) {
                try {
                    TccTransactionContext context = new TccTransactionContext();
                    context.setAction(TccActionEnum.CONFIRMING.getCode());
                    context.setTransId(participant.getTransId());
                    TransactionContextLocal.getInstance().set(context);
                    //通過反射呼叫rpc的confrim方法
                    executeParticipantMethod(participant.getConfirmTccInvocation());
                    participantList.add(participant);
                } catch (Exception e) {
                    LogUtil.error(LOGGER, "執行confirm方法異常:{}", () -> e);
                    success = false;
                    fail = participant;
                    break;
                }
            }
        }
        executeHandler(success, currentTransaction, fail, participantList, participants);
    }
    private void executeHandler(boolean success, final TccTransaction currentTransaction, Participant fail,
                                List<Participant> participantList, final List<Participant> participants) {
        if (success) {
            TransactionContextLocal.getInstance().remove();
            coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.DELETE, currentTransaction));
        } else {
            //獲取還沒執行的,或者執行失敗的
            final List<Participant> updateList =
                    participants.stream().skip(participantList.size()).collect(Collectors.toList());
            currentTransaction.setParticipants(updateList);
            coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.UPDATE, currentTransaction));
            assert fail != null;
            throw new TccRuntimeException(fail.getConfirmTccInvocation().toString());
        }
    }

    private void executeParticipantMethod(TccInvocation tccInvocation) throws Exception {
       if (Objects.nonNull(tccInvocation)) {
           final Class clazz = tccInvocation.getTargetClass();
           final String method = tccInvocation.getMethodName();
           final Object[] args = tccInvocation.getArgs();
           final Class[] parameterTypes = tccInvocation.getParameterTypes();
           final Object bean = SpringBeanUtils.getInstance().getBean(clazz);
           MethodUtils.invokeMethod(bean, method, args, parameterTypes);

       }
   }複製程式碼
  • 這段程式碼的邏輯,簡單理解起來,首先更新當前事務狀態(confrim),獲取當前事務的呼叫點的confrim方法,設定上下文,發起反射呼叫!

  • 其實這裡通過除錯我們發現,發起confrim的方法為 AccountService.payment(AccountDTO accountDTO) ,不過設定的上下文狀態為confrim,
    當我們發起反射呼叫的時候,我們會走到 ProviderTccTransactionHandler.handler 方法,這個方法或許你還有印象,我們再看一下它的程式碼:

@Override
    public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
        TccTransaction tccTransaction = null;
        try {
            switch (TccActionEnum.acquireByCode(context.getAction())) {
                case TRYING:
                    try {
                        //建立事務資訊
                        tccTransaction = tccTransactionManager.providerBegin(context);
                        //發起方法呼叫
                        return point.proceed();
                    } catch (Throwable throwable) {
                        tccTransactionManager.removeTccTransaction(tccTransaction);
                        throw throwable;

                    }
                case CONFIRMING:
                    //如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
                    final TccTransaction acquire = tccTransactionManager.acquire(context);
                    tccTransactionManager.confirm();
                    break;
                case CANCELING:
                    //如果是呼叫CANCELING 通過之前儲存的事務資訊 進行反射呼叫
                    tccTransactionManager.acquire(context);
                    tccTransactionManager.cancel();
                    break;
                default:
                    break;
            }
        } finally {
            tccTransactionManager.remove();
        }
        Method method = ((MethodSignature) (point.getSignature())).getMethod();
        return getDefaultValue(method.getReturnType());
    }複製程式碼
  • 這裡因為上下文設定的狀態為:CONFIRMING ,所以會執行:
//如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
final TccTransaction acquire = tccTransactionManager.acquire(context);
tccTransactionManager.confirm();
break;複製程式碼
  • 我們跟蹤 tccTransactionManager.confirm(); 會發現和之前是一個方法,這時候,你要知道,這個方法是在account微服務裡面執行

  • 所以它最後會執行 AccountServiceImpl.confirm 方法,進行了付款確認。

同理cancel方法也和上面描述的一樣的原理執行。

到這裡,我們就解析完了,整個tcc過程執行的流程,大家關鍵要理解AOP,理解好了切面思想,其實是很簡單的事情了,如果有任何疑問和問題,歡迎加入QQ群:162614487 進行討論

相關文章