[Interpretation of distributed transaction Seata source code 2] Client-side startup process

This article analyzes the client startup process under the AT mode from the source code point of view. The so-called client is the business application side. Distributed transaction is divided into three modules: TC, TM, RM. TC is located on the seata-server side, while TM and RM run on the client side through SDK.

The following figure shows a distributed transaction scenario of Seata's official Demo, which is divided into the following microservices, which jointly implement a distributed transaction of placing orders, deducting inventory, and deducting balance.

  • BusinessService:  business service, the entrance to the order service
  • StorageService:  Inventory microservice , used to deduct commodity inventory
  • OrderService:  order microservice, create order
  • AccountService:  Account microservice , deduct the balance of the user account

Insert picture description here

It can also be seen from the above figure that in the AT mode, the Seata Client mainly implements distributed transactions through the following three modules:

  • GlobalTransactionScanner:  GlobalTransactionScanner is responsible for initializing TM and RM modules, and adding interceptors to the method of adding distributed transaction annotations. The interceptors are responsible for opening, committing or rolling back global transactions
  • DatasourceProxy:  DatasourceProxy adds interception to DataSource. The interceptor intercepts all SQL execution and participates in distributed transaction execution as a participant in the RM transaction.
  • Rpc Interceptor:  In the previous article on Distributed Transaction Seata Source Code Interpretation I mentioned several core points of distributed transactions, one of which is the cross-service instance propagation of distributed transactions. The responsibility of Rpc Interceptor is to spread transactions among multiple microservices.

seata-spring-boot-starter

There are two ways to refer to the seata distributed transaction SDK, relying on seata-all or seata-spring-boot-starter . It is recommended to use seata-spring-boot-starter, because the starter has automatically injected the three modules mentioned above. Just add the corresponding configuration and add the global distributed transaction annotation to the business code. Let's start with the code in the seata-spring-boot-starter project:

The following figure shows the project structure of seata-spring-boot-starter: Insert picture description here 

Mainly divided into the following modules:

  • properties: The  properties directory are all related configuration classes that Springboot adapts to seata, that is, Seata related parameters can be accessed through SpringBoot configuration
  • provider:  The classes in the provider directory are responsible for adapting the configuration of Springboot and SpringCloud to the Seata configuration
  • resources:  There are two main files in the resources directory, spring.factories is used to register Springboot auto-assembly classes, and ExtConfigurationProvider is used to register SpringbootConfigurationProvider classes. The Provider class is responsible for adapting SpringBoot related configuration classes to Seata.

For the springboot-starter project, we first check the resources/META-INF/spring.factories file:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
io.seata.spring.boot.autoconfigure.SeataAutoConfiguration

You can see that the automatic assembly class is configured in spring.factories: SeataAutoConfiguration . Two instances of GlobalTransactionScanner and seataAutoDataSourceProxyCreator are mainly injected into the assembly class . code show as below:

@ComponentScan(basePackages = "io.seata.spring.boot.autoconfigure.properties")
@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = "enabled",
        havingValue = "true",
        matchIfMissing = true)
@Configuration
@EnableConfigurationProperties({SeataProperties.class})
public class SeataAutoConfiguration {

  ...
  
  // GlobalTransactionScanner负责为添加GlobalTransaction注解的方法添加拦截器,
  // 并且负责初始化RM、TM
  @Bean
  @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
  @ConditionalOnMissingBean(GlobalTransactionScanner.class)
  public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties,
                                                           FailureHandler failureHandler) {
    if (LOGGER.isInfoEnabled()) {
      LOGGER.info("Automatically configure Seata");
    }
    return new GlobalTransactionScanner(seataProperties.getApplicationId(),
            seataProperties.getTxServiceGroup(),
            failureHandler);
  }
  
  // SeataAutoDataSourceProxyCreator负责为Spring中的所有DataSource生成代理对象,
  // 从而实现拦截所有SQL的执行
  @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
  @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {
          "enableAutoDataSourceProxy", "enable-auto" +
          "-data-source-proxy"}, havingValue = "true", matchIfMissing = true)
  @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
  public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
    return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
            seataProperties.getExcludesForAutoProxying());
  }
}

GlobalTransactionScanner

GlobalTransactionScanner inherits from AutoProxyCreator . AutoProxyCreator is a way to implement AOP in Spring. It can intercept all instances in Spring and determine whether a proxy is needed. The following lists some of the more important fields in GlobalTransactionScanner and the core methods of intercepting agents:

public class GlobalTransactionScanner extends AbstractAutoProxyCreator
        implements InitializingBean, ApplicationContextAware,
        DisposableBean {
  ...
  // interceptor字段是对应一个代理对象的拦截器,
  // 可以认为是一个临时变量,有效期是一个被代理对象
  private MethodInterceptor interceptor;
  
  // globalTransactionalInterceptor是通用的Interceptor,
  // 非TCC事务方式的都使用该Interceptor
  private MethodInterceptor globalTransactionalInterceptor;
  
  // PROXYED_SET存储已经代理过的实例,防止重复处理
  private static final Set<String> PROXYED_SET = new HashSet<>();
  
  // applicationId是一个服务的唯一标识,
  // 对应springcloud项目中的spring.application.name
  private final String applicationId;
  // 事务的分组标识,参考文章wiki:http://seata.io/zh-cn/docs/user/transaction-group.html
  private final String txServiceGroup;
  
  ...

  // 判断是否需要代理目标对象,如果需要代理,则生成拦截器赋值到类变量interceptor中
  @Override
  protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  	// 判断是否禁用分布式事务
    if (disableGlobalTransaction) {
      return bean;
    }
    try {
      synchronized (PROXYED_SET) {
        if (PROXYED_SET.contains(beanName)) {
          return bean;
        }
        
        // 每次处理一个被代理对象时先把interceptor置为null,所以interceptor的
        // 生命周期是一个被代理对象,由于是在另外一个方法getAdvicesAndAdvisorsForBean
        // 中使用interceptor,所以该interceptor要定义为一个类变量
        interceptor = null;
        
        // 判断是否是TCC事务模式,判断的主要依据是方法上是否有TwoPhaseBusinessAction注解
        if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName,
                applicationContext)) {
          // 创建一个TCC事务的拦截器
          interceptor =
                  new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
        } else {
          // 获取待处理对象的class类型
          Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
          // 获取待处理对象继承的所有接口
          Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
          
          // 如果待处理对象的class或者继承的接口上有GlobalTransactional注解,
          // 或者待处理对象的class的任一个方法上有GlobalTransactional或者
          // GlobalLock注解则返回true,即需要被代理
          if (!existsAnnotation(new Class[]{serviceInterface})
                  && !existsAnnotation(interfacesIfJdk)) {
            return bean;
          }
          
          // 如果interceptor为null,即不是TCC模式,
          // 则使用globalTransactionalInterceptor作为拦截器
          if (interceptor == null) {
            // globalTransactionalInterceptor只会被创建一次
            if (globalTransactionalInterceptor == null) {
              globalTransactionalInterceptor =
                      new GlobalTransactionalInterceptor(failureHandlerHook);
              ConfigurationCache.addConfigListener(
                      ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                      (ConfigurationChangeListener) globalTransactionalInterceptor);
            }
            interceptor = globalTransactionalInterceptor;
          }
        }

        if (!AopUtils.isAopProxy(bean)) {
          // 如果bean本身不是Proxy对象,则直接调用父类的wrapIfNecessary生成代理对象即可
          // 在父类中会调用getAdvicesAndAdvisorsForBean获取到上面定义的interceptor
          bean = super.wrapIfNecessary(bean, beanName, cacheKey);
        } else {
          // 如果该bean已经是代理对象了,则直接在代理对象的拦截调用链AdvisedSupport
          // 上直接添加新的interceptor即可。
          AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
          Advisor[] advisor = buildAdvisors(beanName,
                  getAdvicesAndAdvisorsForBean(null, null, null));
          for (Advisor avr : advisor) {
            advised.addAdvisor(0, avr);
          }
        }         
        // 标识该beanName已经处理过了
        PROXYED_SET.add(beanName);
        return bean;
      }
    } catch (Exception exx) {
      throw new RuntimeException(exx);
    }
  }
  
  // 返回wrapIfNecessary方法中计算出的interceptor对象
  @Override
  protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName,
                                                  TargetSource customTargetSource)
          throws BeansException {
    return new Object[]{interceptor};
  }
}

The above introduces how GlobalTransactionScanner intercepts global transactions through annotations . The specific interceptors are implemented as TccActionInterceptor and GlobalTransactionalInterceptor. For AT mode , we mainly care about GlobalTransactionalInterceptor . In subsequent articles, we will introduce the specific implementation of GlobalTransactionalInterceptor.

In addition, GloabalTransactionScanner is also responsible for the initialization of TM and RM , which is implemented in the initClient method:

private void initClient() {
    ...
    
    //初始化TM
    TMClient.init(applicationId, txServiceGroup);
    ...
    
    //初始化RM
    RMClient.init(applicationId, txServiceGroup);
	...
	
    // 注册Spring shutdown的回调,用来释放资源
    registerSpringShutdownHook();

 }

TMClient and RMClient are both client classes of the Rpc framework implemented by Seata based on Netty , but the business logic is different. Since TMClient is relatively simpler, let's take RMClient as an example to look at the source code:

public class RMClient {
  // RMClient的init是一个static方法,创建了一个RmNettyRemotingClient实例,并调用init方法
  public static void init(String applicationId, String transactionServiceGroup) {
    RmNettyRemotingClient rmNettyRemotingClient =
            RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);
    rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());
    rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());
    rmNettyRemotingClient.init();
  }
}

The implementation of RmNettyRemotingClient is as follows:

@Sharable
public final class RmNettyRemotingClient extends AbstractNettyRemotingClient {
  // ResourceManager负责处理事务参与方,支持AT、TCC、Saga三种模式
  private ResourceManager resourceManager;
  // RmNettyRemotingClient单例
  private static volatile RmNettyRemotingClient instance;
  private final AtomicBoolean initialized = new AtomicBoolean(false);
  // 微服务的唯一标识
  private String applicationId;
  // 分布式事务分组名称
  private String transactionServiceGroup;
  
  // RMClient中init方法会调用该init方法
  public void init() {
    // 注册Seata自定义Rpc的Processor
    registerProcessor();
    if (initialized.compareAndSet(false, true)) {
      // 调用父类的init方法,在父类中负责Netty的初始化,与Seata-Server建立连接
      super.init();
    }
  }
  
  // 注册Seata自定义Rpc的Processor
  private void registerProcessor() {
    // 1.注册Seata-Server发起branchCommit的处理Processor
    RmBranchCommitProcessor rmBranchCommitProcessor =
            new RmBranchCommitProcessor(getTransactionMessageHandler(), this);
    super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT, rmBranchCommitProcessor,
            messageExecutor);
            
    // 2.注册Seata-Server发起branchRollback的处理Processor
    RmBranchRollbackProcessor rmBranchRollbackProcessor =
            new RmBranchRollbackProcessor(getTransactionMessageHandler(), this);
    super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK, rmBranchRollbackProcessor
            , messageExecutor);
            
    // 3.注册Seata-Server发起删除undoLog的处理Processor
    RmUndoLogProcessor rmUndoLogProcessor =
            new RmUndoLogProcessor(getTransactionMessageHandler());
    super.registerProcessor(MessageType.TYPE_RM_DELETE_UNDOLOG, rmUndoLogProcessor,
            messageExecutor);
            
    // 4.注册Seata-Server返回Response的处理Processor,ClientOnResponseProcessor
    // 用于处理由Client主动发起Request,Seata-Server返回的Response。
    // ClientOnResponseProcessor负责把Client发送的Request和Seata-Server
    // 返回的Response对应起来,从而实现Rpc
    ClientOnResponseProcessor onResponseProcessor =
            new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(),
                    getTransactionMessageHandler());
    super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor,
            null);
    super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER_RESULT,
            onResponseProcessor, null);
    super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT,
            onResponseProcessor, null);
    super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY_RESULT,
            onResponseProcessor, null);
    super.registerProcessor(MessageType.TYPE_REG_RM_RESULT, onResponseProcessor, null);
    
    // 5. 处理Seata-Server返回的Pong消息
    ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor();
    super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor,
            null);
  }
}

The above logic looks more complicated, and there are many related classes, such as: various Processors, various MessageType, TransactionMessageHandler, ResourceManager. In fact, it is essentially Rpc call, which is divided into Rm active call and Seata active call .

  • Rm actively calls methods:  such as: register branch, report branch status, apply for global lock, etc. The methods that Rm actively calls need to process the Response returned by Seata-Server in ClientOnResponseProcessor.
  • Seata-Server actively calls methods:  such as: commit branch transaction, roll back branch transaction, delete undolog log. The method that Seata-Server actively calls, the client side corresponds to different Processors to process, and the processing result Response is returned to Seata-Server after processing. The core implementation logic of transaction commit and rollback are in TransactionMessageHandler and ResourceManager.

The specific implementation of TransactionMessageHandler and ResourceManager will also be described in detail in subsequent chapters.

The next article will introduce how SeataAutoDataSourceProxyCreator and Rpc Interceptor are initialized and intercepted.

Original: https://seata.io/zh-cn/blog/seata-sourcecode-client-bootstrap.html

●The strongest Tomcat8 performance optimization in history

Why can Alibaba resist 10 billion in 90 seconds? --The evolution of server-side high-concurrency distributed architecture

B2B e-commerce platform--ChinaPay UnionPay electronic payment function

Learn Zookeeper distributed lock, let interviewers look at you with admiration

SpringCloud e-commerce spike microservice-Redisson distributed lock solution

Check out more good articles, enter the official account--please me--excellent in the past

A deep and soulful public account 0.0

Guess you like

Origin blog.csdn.net/a1036645146/article/details/108800319