tcc分布式事务源码解析系列(四)之项目实战

通过之前的几篇文章我相信您已经搭建好了运行环境,本次的项目实战是依照happylifeplat-tcc-demo项目来演练,也是非常经典的分布式事务场景:支付成功,进行订单状态的更新,扣除用户账户,库存扣减这几个模块来进行tcc分布式事务。话不多说,让我们一起进入体验吧!

  • 首先我们找到 PaymentServiceImpl.makePayment 方法,这是tcc分布式事务的发起者
   //tcc分布式事务的发起者
   @Tcc(confirmMethod = "confirmOrderStatus", cancelMethod = "cancelOrderStatus")
   public void makePayment(Order order) {
       order.setStatus(OrderStatusEnum.PAYING.getCode());
       orderMapper.update(order);
       //扣除用户余额 远端的rpc调用
       AccountDTO accountDTO = new AccountDTO();
       accountDTO.setAmount(order.getTotalAmount());
       accountDTO.setUserId(order.getUserId());
       accountService.payment(accountDTO);
       //进入扣减库存操作 远端的rpc调用
       InventoryDTO inventoryDTO = new InventoryDTO();
       inventoryDTO.setCount(order.getCount());
       inventoryDTO.setProductId(order.getProductId());
       inventoryService.decrease(inventoryDTO);
   }
   //更新订单的confirm方法
   public void confirmOrderStatus(Order order) {
        order.setStatus(OrderStatusEnum.PAY_SUCCESS.getCode());
        orderMapper.update(order);
        LOGGER.info("=========进行订单confirm操作完成================");


    }
    //更新订单的cancel方法
    public void cancelOrderStatus(Order order) {
        order.setStatus(OrderStatusEnum.PAY_FAIL.getCode());
        orderMapper.update(order);
        LOGGER.info("=========进行订单cancel操作完成================");
    }

  • makePayment 方法是tcc分布式事务的发起者,它里面有更新订单状态(本地),进行库存扣减(rpc扣减),资金扣减(rpc调用)

  • confirmOrderStatus 为订单状态confirm方法,cancelOrderStatus 为订单状态cancel方法。

  • 我们重点关注 @Tcc(confirmMethod = "confirmOrderStatus", cancelMethod = "cancelOrderStatus") 这个注解,为什么加了这个注解就有神奇的作用。

@Tcc注解切面

@Aspect
public abstract class TccTransactionAspect {

    private TccTransactionInterceptor tccTransactionInterceptor;

    public void setTccTransactionInterceptor(TccTransactionInterceptor tccTransactionInterceptor) {
        this.tccTransactionInterceptor = tccTransactionInterceptor;
    }

    @Pointcut("@annotation(com.happylifeplat.tcc.annotation.Tcc)")
    public void txTransactionInterceptor() {

    }

    @Around("txTransactionInterceptor()")
    public Object interceptCompensableMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return tccTransactionInterceptor.interceptor(proceedingJoinPoint);
    }

    public abstract int getOrder();
}

  • 我们可以知道Spring实现类的方法凡是加了@Tcc注解的,在调用的时候,都会进行 tccTransactionInterceptor.interceptor 调用。

  • 其次我们发现该类是一个抽象类,肯定会有其他类继承它,你猜的没错,对应dubbo用户,他的继承类为:

@Aspect
@Component
public class DubboTccTransactionAspect extends TccTransactionAspect implements Ordered {


    @Autowired
    public DubboTccTransactionAspect(DubboTccTransactionInterceptor dubboTccTransactionInterceptor) {
        super.setTccTransactionInterceptor(dubboTccTransactionInterceptor);
    }


    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

  • springcloud的用户,它的继承类为:
@Aspect
@Component
public class SpringCloudTxTransactionAspect extends TccTransactionAspect implements Ordered {


    @Autowired
    public SpringCloudTxTransactionAspect(SpringCloudTxTransactionInterceptor  springCloudTxTransactionInterceptor) {
        this.setTccTransactionInterceptor(springCloudTxTransactionInterceptor);
    }


    public void init() {

    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

  • 我们注意到他们都实现了Spring的Ordered接口,并重写了 getOrder 方法,都返回了 Ordered.HIGHEST_PRECEDENCE 那么可以知道,他是优先级最高的切面。

TccCoordinatorMethodAspect 切面

@Aspect
@Component
public class TccCoordinatorMethodAspect implements Ordered {


    private final TccCoordinatorMethodInterceptor tccCoordinatorMethodInterceptor;

    @Autowired
    public TccCoordinatorMethodAspect(TccCoordinatorMethodInterceptor tccCoordinatorMethodInterceptor) {
        this.tccCoordinatorMethodInterceptor = tccCoordinatorMethodInterceptor;
    }


    @Pointcut("@annotation(com.happylifeplat.tcc.annotation.Tcc)")
    public void coordinatorMethod() {

    }

    @Around("coordinatorMethod()")
    public Object interceptCompensableMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return tccCoordinatorMethodInterceptor.interceptor(proceedingJoinPoint);
    }


    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}

  • 该切面是第二优先级的切面,意思就是当我们对有@Tcc注解的方法发起调用的时候,首先会进入 TccTransactionAspect 切面,然后再进入 TccCoordinatorMethodAspect 切面。
    该切面主要是用来获取@Tcc注解上的元数据,比如confrim方法名称等等。

  • 到这里如果大家基本能明白的话,差不多就了解了整个框架原理,现在让我们来跟踪调用吧。

现在我们回过头,我们来调用 PaymentServiceImpl.makePayment 方法

  • dubbo用户,我们会进入如下拦截器:

@Component
public class DubboTccTransactionInterceptor implements TccTransactionInterceptor {

    private final TccTransactionAspectService tccTransactionAspectService;

    @Autowired
    public DubboTccTransactionInterceptor(TccTransactionAspectService tccTransactionAspectService) {
        this.tccTransactionAspectService = tccTransactionAspectService;
    }


    @Override
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        final String context = RpcContext.getContext().getAttachment(Constant.TCC_TRANSACTION_CONTEXT);
        TccTransactionContext tccTransactionContext = null;
        if (StringUtils.isNoneBlank(context)) {
            tccTransactionContext =
                    GsonUtils.getInstance().fromJson(context, TccTransactionContext.class);
        }
        return tccTransactionAspectService.invoke(tccTransactionContext, pjp);
    }
}

  • 我们继续跟踪 tccTransactionAspectService.invoke(tccTransactionContext, pjp) ,发现通过判断过后,我们会进入 StartTccTransactionHandler.handler 方法,我们已经进入了分布式事务处理的入口了!
/**
    * 分布式事务处理接口
    *
    * @param point   point 切点
    * @param context 信息
    * @return Object
    * @throws Throwable 异常
    */
   @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;
   }

  • 我们进行跟进 tccTransactionManager.begin() 方法:
/**
    * 该方法为发起方第一次调用
    * 也是tcc事务的入口
    */
   void begin() {
       LogUtil.debug(LOGGER, () -> "开始执行tcc事务!start");
       TccTransaction tccTransaction = CURRENT.get();
       if (Objects.isNull(tccTransaction)) {
           tccTransaction = new TccTransaction();
           tccTransaction.setStatus(TccActionEnum.TRYING.getCode());
           tccTransaction.setRole(TccRoleEnum.START.getCode());
       }
       //保存当前事务信息
       coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.SAVE, tccTransaction));

       CURRENT.set(tccTransaction);

       //设置tcc事务上下文,这个类会传递给远端
       TccTransactionContext context = new TccTransactionContext();
       context.setAction(TccActionEnum.TRYING.getCode());//设置执行动作为try
       context.setTransId(tccTransaction.getTransId());//设置事务id
       TransactionContextLocal.getInstance().set(context);

   }
  • 这里我们保存了事务信息,并且开启了事务上下文,并把它保存在了ThreadLoacl里面,大家想想这里为什么一定要保存在ThreadLocal里面。

  • begin方法执行完后,我们回到切面,现在我们来执行 point.proceed(),当执行这一句代码的时候,会进入第二个切面,即进入了 TccCoordinatorMethodInterceptor


public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {

       final TccTransaction currentTransaction = tccTransactionManager.getCurrentTransaction();

       if (Objects.nonNull(currentTransaction)) {
           final TccActionEnum action = TccActionEnum.acquireByCode(currentTransaction.getStatus());
           switch (action) {
               case TRYING:
                   registerParticipant(pjp, currentTransaction.getTransId());
                   break;
               case CONFIRMING:
                   break;
               case CANCELING:
                   break;
           }
       }
       return pjp.proceed(pjp.getArgs());
   }

  • 这里由于是在try阶段,直接就进入了 registerParticipant 方法
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);

    }
  • 这里就获取了 @Tcc(confirmMethod = "confirmOrderStatus", cancelMethod = "cancelOrderStatus")
    的信息,并把他封装成了 Participant,存起来,然后真正的调用 PaymentServiceImpl.makePayment 业务方法,在业务方法里面,我们首先执行的是更新订单状态(相信现在你还记得0.0):
       order.setStatus(OrderStatusEnum.PAYING.getCode());
       orderMapper.update(order);
  • 接下来,我们进行扣除用户余额,注意扣除余额这里是一个rpc方法:
       AccountDTO accountDTO = new AccountDTO();
       accountDTO.setAmount(order.getTotalAmount());
       accountDTO.setUserId(order.getUserId());
       accountService.payment(accountDTO)
  • 现在我们来关注 accountService.payment(accountDTO) ,这个接口的定义。

dubbo接口:


public interface AccountService {


    /**
     * 扣款支付
     *
     * @param accountDTO 参数dto
     * @return true
     */
    @Tcc
    boolean payment(AccountDTO accountDTO);
}

springcloud接口

@FeignClient(value = "account-service", configuration = MyConfiguration.class)
public interface AccountClient {

    @PostMapping("/account-service/account/payment")
    @Tcc
    Boolean payment(@RequestBody AccountDTO accountDO);

}

很明显这里我们都在接口上加了@Tcc注解,我们知道springAop的特性,在接口上加注解,是无法进入切面的,所以我们在这里,要采用rpc框架的某些特性来帮助我们获取到 @Tcc注解信息。 这一步很重要。当我们发起

accountService.payment(accountDTO) 调用的时候:

  • dubbo用户,会走dubbo的filter接口,TccTransactionFilter:
private void registerParticipant(Class clazz, String methodName, Object[] arguments, Class... args) throws TccRuntimeException {
      try {
          Method method = clazz.getDeclaredMethod(methodName, args);
          Tcc tcc = method.getAnnotation(Tcc.class);
          if (Objects.nonNull(tcc)) {

            //获取事务的上下文
              final TccTransactionContext tccTransactionContext =
                      TransactionContextLocal.getInstance().get();
              if (Objects.nonNull(tccTransactionContext)) {
                //dubbo rpc传参数
                  RpcContext.getContext()
                          .setAttachment(Constant.TCC_TRANSACTION_CONTEXT,
                                  GsonUtils.getInstance().toJson(tccTransactionContext));
              }
              if (Objects.nonNull(tccTransactionContext)) {
                  if(TccActionEnum.TRYING.getCode()==tccTransactionContext.getAction()){
                      //获取协调方法
                      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 = new TccInvocation(clazz,
                              confirmMethodName,
                              args, arguments);

                      TccInvocation cancelInvocation = new TccInvocation(clazz,
                              cancelMethodName,
                              args, arguments);

                      //封装调用点
                      final Participant participant = new Participant(
                              tccTransactionContext.getTransId(),
                              confirmInvocation,
                              cancelInvocation);

                      tccTransactionManager.enlistParticipant(participant);
                  }

              }



          }
      } catch (NoSuchMethodException e) {
          throw new TccRuntimeException("not fount method " + e.getMessage());
      }

  }
  • springcloud 用户,则会进入 TccFeignHandler
public class TccFeignHandler implements InvocationHandler {
    /**
     * logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(TccFeignHandler.class);

    private Target<?> target;
    private Map<Method, MethodHandler> handlers;

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {

            final Tcc tcc = method.getAnnotation(Tcc.class);
            if (Objects.isNull(tcc)) {
                return this.handlers.get(method).invoke(args);
            }

            final TccTransactionContext tccTransactionContext =
                    TransactionContextLocal.getInstance().get();
            if (Objects.nonNull(tccTransactionContext)) {
                final TccTransactionManager tccTransactionManager =
                        SpringBeanUtils.getInstance().getBean(TccTransactionManager.class);
                if (TccActionEnum.TRYING.getCode() == tccTransactionContext.getAction()) {
                    //获取协调方法
                    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());

                    final Class<?> declaringClass = method.getDeclaringClass();

                    TccInvocation confirmInvocation = new TccInvocation(declaringClass,
                            confirmMethodName,
                            method.getParameterTypes(), args);

                    TccInvocation cancelInvocation = new TccInvocation(declaringClass,
                            cancelMethodName,
                            method.getParameterTypes(), args);

                    //封装调用点
                    final Participant participant = new Participant(
                            tccTransactionContext.getTransId(),
                            confirmInvocation,
                            cancelInvocation);

                    tccTransactionManager.enlistParticipant(participant);
                }

            }


            return this.handlers.get(method).invoke(args);
        }
    }


    public void setTarget(Target<?> target) {
        this.target = target;
    }


    public void setHandlers(Map<Method, MethodHandler> handlers) {
        this.handlers = handlers;
    }

}

重要提示 在这里我们获取了在第一步设置的事务上下文,然后进行dubbo的rpc传参数,细心的你可能会发现,这里获取远端confirm,cancel方法其实就是自己本身啊?,那发起者还怎么来调用远端的confrim,cancel方法呢?这里的调用,我们通过事务上下文的状态来控制,比如在confrim阶段,我们就调用confrim方法等。。

到这里我们就完成了整个消费端的调用,下一篇分析提供者的调用流程!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容