阿里中间件seata源码剖析五:聊聊seata中全局事务的开启

在之前的文章《springcloud+eureka整合seata-tcc模式》中,我写了一个使用seata实现TCC模式的demo,这个demo中,我们使用了springcloud+eureka来实现的微服务,其中包括订单服务、账户服务和库存服务,服务聚合在订单这个服务。
 

我们再来看一下TCC的官方流程图,RM都注册到TC,业务开始后,TM发起全局事务,RM报告分支事务状态,如果都prepare成功,则TC会通知各个服务依次调用分支事务的commit方法,否则TC调用分支事务的rollback方法。
 这里,订单服务作为一个TM,会开启一个全局事务,本文我们就来聊一聊全局事务的开启过程。

下面我们再看一段全局事务提交成功的seata server的日志:

2020-09-23 01:08:31.624  INFO --- [Thread_1_43_500] i.s.s.coordinator.DefaultCoordinator     : Begin new global transaction applicationId: order-server,transactionServiceGroup: my_test_tx_group, transactionName: create(io.seata.sample.entity.Order),timeout:60000,xid:192.168.59.143:8091:52019904118321152
2020-09-23 01:08:31.679  INFO --- [Thread_1_44_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52019904118321152, branchId = 52019904344813568, resourceId = orderApi ,lockKeys = null
2020-09-23 01:08:31.680  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52019904118321152,branchType=TCC,resourceId=orderApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:08:32.483  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52019904118321152,branchType=TCC,resourceId=storageApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:08:32.483  INFO --- [Thread_1_45_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52019904118321152, branchId = 52019907721228288, resourceId = storageApi ,lockKeys = null
2020-09-23 01:08:32.559  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52019904118321152,branchType=TCC,resourceId=accountApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:08:32.559  INFO --- [Thread_1_46_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52019904118321152, branchId = 52019908039995392, resourceId = accountApi ,lockKeys = null
2020-09-23 01:08:32.579  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52019904118321152,extraData=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:08:32.627  INFO --- [Thread_1_47_500] io.seata.server.coordinator.DefaultCore  : Committing global transaction is successfully done, xid = 192.168.59.143:8091:52019904118321152.

下面我们再看一段全局事务失败回滚的seata server的日志:

2020-09-23 01:30:55.806  INFO --- [Thread_1_10_500] i.s.s.coordinator.DefaultCoordinator     : Begin new global transaction applicationId: order-server,transactionServiceGroup: my_test_tx_group, transactionName: create(io.seata.sample.entity.Order),timeout:60000,xid:192.168.59.143:8091:52025542022066176
2020-09-23 01:30:55.808  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage timeout=60000,transactionName=create(io.seata.sample.entity.Order)
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:30:55.812  INFO --- [Thread_1_11_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52025542022066176, branchId = 52025542051426304, resourceId = orderApi ,lockKeys = null
2020-09-23 01:30:55.817  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52025542022066176,branchType=TCC,resourceId=orderApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:30:56.059  INFO --- [rThread_1_8_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52025542022066176, branchId = 52025543087419392, resourceId = storageApi ,lockKeys = null
2020-09-23 01:30:56.059  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52025542022066176,branchType=TCC,resourceId=storageApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:30:56.442  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52025542022066176,branchType=TCC,resourceId=accountApi,lockKey=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:30:56.442  INFO --- [Thread_1_12_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.59.143:8091:52025542022066176, branchId = 52025544693837824, resourceId = accountApi ,lockKeys = null
2020-09-23 01:30:56.737  INFO --- [LoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.59.143:8091:52025542022066176,extraData=null
,clientIp:192.168.59.1,vgroup:my_test_tx_group
2020-09-23 01:30:56.757  INFO --- [Thread_1_13_500] io.seata.server.coordinator.DefaultCore  : Rollback branch transaction successfully, xid = 192.168.59.143:8091:52025542022066176 branchId = 52025544693837824
2020-09-23 01:30:56.771  INFO --- [Thread_1_13_500] io.seata.server.coordinator.DefaultCore  : Rollback branch transaction successfully, xid = 192.168.59.143:8091:52025542022066176 branchId = 52025543087419392
2020-09-23 01:30:56.777  INFO --- [Thread_1_13_500] io.seata.server.coordinator.DefaultCore  : Rollback branch transaction successfully, xid = 192.168.59.143:8091:52025542022066176 branchId = 52025542051426304
2020-09-23 01:30:56.778  INFO --- [Thread_1_13_500] io.seata.server.coordinator.DefaultCore  : Rollback global transaction successfully, xid = 192.168.59.143:8091:52025542022066176.

TM发送全局事务请求

开始之前,我们还是看一下我们的demo中代码:

@GlobalTransactional
public boolean create(Order order) {
    String xid = RootContext.getXID();
    LOGGER.info("------->交易开始");
    BusinessActionContext actionContext = new BusinessActionContext();
    actionContext.setXid(xid);
    boolean result = orderSaveImpl.saveOrder(actionContext, order);
    if(!result){
        throw new RuntimeException("保存订单失败");
    }
    //远程方法 扣减库存
    LOGGER.info("------->扣减库存开始storage中");
    result = storageApi.decrease(actionContext, order.getProductId(), order.getCount());
    if(!result){
        throw new RuntimeException("扣减库存失败");
    }
    LOGGER.info("------->扣减库存结束storage中");
    //远程方法 扣减账户余额
    LOGGER.info("------->扣减账户开始account中");
    result = accountApi.prepare(actionContext, order.getUserId(),order.getPayAmount());
    LOGGER.info("------->扣减账户结束account中" + result);
    LOGGER.info("------->交易结束");
    throw new RuntimeException("调用2阶段提交的rollback方法");
    //return true;
}

之前讲过,开启全局事务的注解是@GlobalTransactional,这个是使用spring的aop来实现的。代理类是GlobalTransactionalInterceptor,我们就从这个类作为入口来看。作为代理类,我们看一下invoke方法:

public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
    Class<?> targetClass =
        methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;//io.seata.sample.service.OrderServiceImpl
    Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);//create方法
    if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
        final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);//create方法
        final GlobalTransactional globalTransactionalAnnotation =
            getAnnotation(method, targetClass, GlobalTransactional.class);
        final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
        boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
        if (!localDisable) {
            if (globalTransactionalAnnotation != null) {
                return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);//全局事务走这个分支
            } else if (globalLockAnnotation != null) {
                return handleGlobalLock(methodInvocation);
            }
        }
    }
    return methodInvocation.proceed();//走不到这儿
}
private Object handleGlobalTransaction(final MethodInvocation methodInvocation,
    final GlobalTransactional globalTrxAnno) throws Throwable {
    boolean succeed = true;
    try {
        return transactionalTemplate.execute(new TransactionalExecutor() {//execute方法会通知TC开始全局事务
            @Override
            public Object execute() throws Throwable {
                return methodInvocation.proceed();//调用被代理方法,即上面demo的create方法
            }

            public String name() {
                String name = globalTrxAnno.name();
                if (!StringUtils.isNullOrEmpty(name)) {
                    return name;
                }
                return formatMethod(methodInvocation.getMethod());
            }

            @Override
            public TransactionInfo getTransactionInfo() {
                TransactionInfo transactionInfo = new TransactionInfo();
                transactionInfo.setTimeOut(globalTrxAnno.timeoutMills());
                transactionInfo.setName(name());
                transactionInfo.setPropagation(globalTrxAnno.propagation());
                Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
                for (Class<?> rbRule : globalTrxAnno.rollbackFor()) {
                    rollbackRules.add(new RollbackRule(rbRule));
                }
                for (String rbRule : globalTrxAnno.rollbackForClassName()) {
                    rollbackRules.add(new RollbackRule(rbRule));
                }
                for (Class<?> rbRule : globalTrxAnno.noRollbackFor()) {
                    rollbackRules.add(new NoRollbackRule(rbRule));
                }
                for (String rbRule : globalTrxAnno.noRollbackForClassName()) {
                    rollbackRules.add(new NoRollbackRule(rbRule));
                }
                transactionInfo.setRollbackRules(rollbackRules);//rollbackRules是空集合
                return transactionInfo;
            }
        });
    } catch (TransactionalExecutor.ExecutionException e) {
        //省略异常处理
        }
    } finally {
        if (degradeCheck) {
            EVENT_BUS.post(new DegradeCheckEvent(succeed));//Google guava提供的观察者模式,监听者在当前类的onDegradeCheck方法,自动服务降级和恢复,这里暂时不详细讨论
        }
    }
}

下面我们讲一下上面方法调用的execute方法,在TransactionalTemplate类:

public Object execute(TransactionalExecutor business) throws Throwable {
     // 1 get transactionInfo
     TransactionInfo txInfo = business.getTransactionInfo();//生成逻辑在上面方法
     //省略部分代码
     // 1.1 get or create a transaction
     GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
     // 1.2 Handle the Transaction propatation and the branchType
     Propagation propagation = txInfo.getPropagation();
     SuspendedResourcesHolder suspendedResourcesHolder = null;
     try {
         //事务传播机制的代码省略
         try {
             // 2. begin transaction
             beginTransaction(txInfo, tx);
             Object rs = null;
             try {

                 // Do Your Business
                 rs = business.execute();//触发被代理方法,即demo中的create
             } catch (Throwable ex) {
                 // 3.the needed business exception to rollback.
                 completeTransactionAfterThrowing(txInfo, tx, ex);//回滚事务
                 throw ex;
             }

             // 4. everything is fine, commit.
             commitTransaction(tx);//提交事务
             return rs;
         } finally {
             //5. clear
             triggerAfterCompletion();
             cleanUp();
         }
     } finally {
         tx.resume(suspendedResourcesHolder);
     }

 }

上面的beginTransaction方法会调用TC开启全局事务然后获得xid:

private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
    tx.begin(txInfo.getTimeOut(), txInfo.getName());//省略其他代码
}

上面的begin方法调用的是DefaultGlobalTransaction的begin,代码如下:

public void begin(int timeout, String name) throws TransactionException {
    //这里代码省略,都是判断xid是否为空,为空则抛出异常
    xid = transactionManager.begin(null, null, name, timeout);
    status = GlobalStatus.Begin;
    RootContext.bind(xid);
}

接着调用DefaultTransactionManager的begin方法,代码如下:

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);//使用netty向TC发送开启全局事务的请求
    if (response.getResultCode() == ResultCode.Failed) {
        throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
    }
    return response.getXid();
}

 可见,开启全局事务TM首先会向TC发送通知,获得xid后再进行本地事务rpc调用,这就跟TCC的官方流程图是一致的。

 总结一下这个过程的UML类图如下:

 

 TC处理全局事务请求

那么在TC端是怎么处理的呢?我们还是从TC server初始化讲起:

netty server初始化之后,处理请求的类是ServerHandler,这个对象的初始化在AbstractNettyRemotingServer的构造函数,代码如下:

public AbstractNettyRemotingServer(ThreadPoolExecutor messageExecutor, NettyServerConfig nettyServerConfig) {
    super(messageExecutor);
    serverBootstrap = new NettyServerBootstrap(nettyServerConfig);
    serverBootstrap.setChannelHandlers(new ServerHandler());
}

 AbstractNettyRemotingServer中的channelRead方法如下:

public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
    if (!(msg instanceof RpcMessage)) {
        return;
    }
    processMessage(ctx, (RpcMessage) msg);
}

我们跟踪它处理的ServerOnRequestProcessor,在onRequestMessage方法中对DefaultCoordinator的调用,调用方法如下:

public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) {
    if (!(request instanceof AbstractTransactionRequestToTC)) {//这里的request就是GlobalBeginRequest
        throw new IllegalArgumentException();
    }
    AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request;
    transactionRequest.setTCInboundHandler(this);

    return transactionRequest.handle(context);
}

我们跟踪上面的代码,最后调用了GlobalBeginRequest的handle方法,代码如下:

public AbstractTransactionResponse handle(RpcContext rpcContext) {
    return handler.handle(this, rpcContext);
}

上面的handler调用的是AbstractTCInboundHandler中的handle,代码如下:

public GlobalBeginResponse handle(GlobalBeginRequest request, final RpcContext rpcContext) {
    GlobalBeginResponse response = new GlobalBeginResponse();
    exceptionHandleTemplate(new AbstractCallback<GlobalBeginRequest, GlobalBeginResponse>() {
        @Override
        public void execute(GlobalBeginRequest request, GlobalBeginResponse response) throws TransactionException {
            try {
                doGlobalBegin(request, response, rpcContext);
            } catch (StoreException e) {
                throw new TransactionException(TransactionExceptionCode.FailedStore,
                    String.format("begin global request failed. xid=%s, msg=%s", response.getXid(), e.getMessage()),
                    e);
            }
        }
    }, request, response);
    return response;
}

最终调用了DefaultCoordinator中的doGlobalBegin方法,代码如下:

protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)
    throws TransactionException {
    response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),
        request.getTransactionName(), request.getTimeout()));//这里产生了xid
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}",
            rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());
    }
}

上面的core.begin调用了DefaultCore中的begin方法,代码如下:

public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
    throws TransactionException {
    GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
        timeout);//继续跟踪createGlobalSession方法,会发现xid是通过雪花算法生成的自增id
    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();
}

总结一下上面的类调用关系,UML图如下:

 这张图上面的部分给出的是Server端从初始化到处理请求消息的相关类,而下面的部分则是消息的类型,本文我们讨论的消息类型是GlobalBeginRequest。

总结

seata开启全局事务的流程简单讲就是TM向TC发送一个申请,TC收到请求后,创建一个session,并用雪花算法生成一个xid返回给RM。

这篇文章的UML类图不仅仅是开启全局事务这个流程可以用,之后全局事务提交、回滚等流程也是可以用的。看懂了这几个图,对理解seata全局事务源码有很大的帮助。

源代码理解不正确的地方欢迎大佬们批评指正。


 seata专栏回顾:

springcloud+eureka整合阿里seata-saga模式

阿里中间件seata源码剖析四:AT模式中undolog实现

 《阿里中间件seata源码剖析三:聊聊seata中的ShutdownHook

阿里中间件seata源码剖析二:聊聊TC的初始化

阿里中间件seata源码剖析一:聊聊RM和TM客户端初始化

springcloud+eureka整合seata-tcc模式

springboot多数据源整合seata-AT模式

springcloud+eureka整合seata-AT模式


                                                                              欢迎关注个人公众号

                                                                    

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君哥聊技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值