seata源码分析(AT)-开始事务

本文详细分析了Seata分布式事务解决方案中的AT模式,从源码层面探讨了全局事务的启动过程。首先介绍了Seata支持的几种模式,重点讲解了AT模式的零侵入性和性能优势。接着,通过源码分析展示了Seata如何通过AOP切入方法,开始事务,包括客户端的事务注册、服务端的事务处理以及远程调用中全局事务ID的传递。文章还简要阐述了Seata的工作原理,并给出了源码结构图,帮助读者理解Seata全局事务的内部机制。

一、seata 模式介绍

seata作为一款优秀的分布式事务解决方案,了解的它底层原理是非常有必要的,这篇的源码分析只分析它的AT模式,它支持很多的模式,比如XA模式,TCC模式、SAGA模式,但是现在用的最多的还是AT模式,AT模式是业务零侵入,而且在性能方面也是得到了保证的,需要注意的是AT模式保证的是最终一致性,而不是强一致性,我们都知道在CAP架构中,如果是AP架构就是最终一致性,如果是CP架构,那么就是强一致性,但是为了高可用,有些开源中间件对外开放的是最终一致性,而强一致性导致的后果就是如果出现了问题为了保证强一致性,那么是没有办法实现高可用的,所以为了保证高可用,采用AP架构是比较常用的,比如nacos注册中心;而AT、TCC、 XA这三个模式算是用的比较多的:
AT:事务期间的分支异步处理,及时释放资源,性能更好;
TCC:提供了二阶段的预处理、提交和回滚,开发者可以指定提交和回滚的方法,业务的侵入性较强,需要开发者去处理具体的事务提交和回滚;
XA:这种模式保证的是强一致性,也就是在事务期间,所有的分支事务都是对资源强拥有,是不会释放资源的,只有最后事务完成才会释放资源,所以性能上会差点,但是用于金钱转账的场景是必须的,因为转账的场景必须是强一致性。

作为一款分布式事务的解决方案,当然AT模式是首推的模式,对业务的零侵入,一个注解搞定,而且性能上也有保证,所以也是使用的最多的一种模式,所以这里就只分析AT模式。

二、源码分析

1、分布式事务的原理猜测

seata的源码从哪里入手呢?上一篇比较已经知道了如何使用,只要TC配置好了以后,使用就非常简单,就是一个注解搞定,@GlobalTransactional这注解加在方法上就是一个TM,TM就是事务的发起者也是事务管理器,分支事务是一个RM,就是一个资源管理器,分析过这么多的开源框架,用脚指头都知道了这个注解肯定被spring切了,肯定会要想要是aop做的处理,其实对这个分布式事务的简单理解可以认为是:

TM(事务发起者):
try{
    //开始事务,注册全局事务XID
    begin();
    //调用远程接口(如果是open fegin 将xid传入到下一个分支事务节点)
    process();
    //事务完成以后提交所有的分支事务
    commit();
}catch(。。。){
  //出现异常,回滚所有的分支事务
  rollback();
}finally{
    //关闭事务管理器TM
    close();
}

seata的分布式事务其实就是上面的伪代码的逻辑差不多的,当然了它做了很多的处理
1.首先要扫描到加了@GlobalTransactional注解的bean做一个切面,也就是一个advice,然后将这个bean做一个切面aop放入容器;
2.当你调用加了@GlobalTransactional注解的方法的时候,会进入bean的切面aop去执行,执行的逻辑就如上面的伪代码。

2、源码入口

现在的问题是要找到源码的入口,我这里有两种方式,一种很直接,还有一种就是通过spring boot的自动装配去找,最简单还是猜测去找,但是不知道其原理,我们都知道spring的aop的切面有一个方法拦截器接口MethodInterceptor,所以可以通过这种方式快速定位

在这里插入图片描述
这样找就非常快,seata的命名规则还是非常规范的,这样就非常快的找到了这个拦截器了,但是这种模式是建立在你对其很熟悉而且作者的类的命名很规范的情况下可以这样做;还有一种方式就是一步一步的去找,这种方式适用于所有的开源框架;
1.根据引入的开源框架seata,找到的它的包,打开如:
在这里插入图片描述
2.直接看spring.factories自动装配文件就可以去找到了GlobalTransactionAutoConfiguration这个自动装配的注入类,然后在里面有一个方法

@Bean
public GlobalTransactionScanner globalTransactionScanner() {

   String applicationName = applicationContext.getEnvironment()
         .getProperty("spring.application.name");

   String txServiceGroup = seataProperties.getTxServiceGroup();

   if (StringUtils.isEmpty(txServiceGroup)) {
      txServiceGroup = applicationName + "-seata-service-group";
      seataProperties.setTxServiceGroup(txServiceGroup);
   }

   return new GlobalTransactionScanner(applicationName, txServiceGroup);
}

很显然,构建这个拦截器就在GlobalTransactionScanner中;
3、我们看这个类GlobalTransactionScanner的实现
在这里插入图片描述
很自然就想到了spring的aop了,所以看到这个继承,就可以直接看它的wrapIfNecessary,这个方法是在spring bean的初始化后会调用是否有切面advise的时候来调用的,所以就要看这个方法

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    try {
        synchronized (PROXYED_SET) {
            //缓存的实现,如果找到了直接返回
            if (PROXYED_SET.contains(beanName)) {
                return bean;
            }
            interceptor = null;
            //check TCC proxy
            //TCC模式的aop实现
            if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
                //TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
                interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
                ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                    (ConfigurationChangeListener)interceptor);
            } else {
                //AT模式的实现
                Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
                Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);

                //这个if判断的是否这个bean或者方法是否加了@GlobalTransactional注解,如果加了这个注解
                //如果加了这个注解就代表要做一个advise,也就是切面逻辑
                if (!existsAnnotation(new Class[]{serviceInterface})
                    && !existsAnnotation(interfacesIfJdk)) {
                    return bean;
                }

                //构建advise的切面类GlobalTransactionalInterceptor,所以这里就是找到切面的逻辑拦截器
                if (interceptor == null) {
                    if (globalTransactionalInterceptor == null) {
                        globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
                        ConfigurationCache.addConfigListener(
                            ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                            (ConfigurationChangeListener)globalTransactionalInterceptor);
                    }
                    interceptor = globalTransactionalInterceptor;
                }
            }

            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 {
                //构建advise返回
                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);
    }
}

所以这种方式也是可以找到GlobalTransactionalInterceptor的,我建议是通过这种方式去找,当然你熟悉了它的尿性,就可以直接定位。

3、GlobalTransactionalInterceptor分析

GlobalTransactionalInterceptor是一个方法拦截器,那么方法拦截器肯定要执行里面的invoke方法了,执行所以这里直接切到invoke方法,seata我这里也只能分析它的主体流程,太细的不会去分析,没有必要,所以这里直接切到它的invoke方法

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);
    if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
        //找到方法
        final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
        //得到方法上的注解
        final GlobalTransactional globalTransactionalAnnotation =
            getAnnotation(method, targetClass, GlobalTransactional.class);
        final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
        boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
        if (!localDisable) {
            //如果加了@GlobalTransactional注解,调用handleGlobalTransaction
            if (globalTransactionalAnnotation != null) {
                return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
            } else if (globalLockAnnotation != null) {
                return handleGlobalLock(methodInvocation, globalLockAnnotation);
            }
        }
    }
    return methodInvocation.proceed();
}

3.1、handleGlobalTransaction分析

3.1.1、客户端的处理

handleGlobalTransaction中处理的就是全局事务的核心逻辑,比如事务的传播特性的判断、全局事务的注册,业务逻辑的执行、事务的提交和回滚等等

Object handleGlobalTransaction(final MethodInvocation methodInvocation,
    final GlobalTransactional globalTrxAnno) throws Throwable {
    boolean succeed = true;
    try {
        //采用模板方法处理全局事务
        return transactionalTemplate.execute(new TransactionalExecutor() {
            @Override
            public Object execute() throws Throwable {
                return methodInvocation.proceed();
            }

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

            /**
             *获取事务对象信息
             * @return
             */
            @Override
            public TransactionInfo getTransactionInfo() {
                // reset the value of timeout
                int timeout = globalTrxAnno.timeoutMills();
                if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
                    timeout = defaultGlobalTransactionTimeout;
                }

                TransactionInfo transactionInfo = new TransactionInfo();
                transactionInfo.setTimeOut(timeout);
                transactionInfo.setName(name());
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值