[Interprétation du code source Seata de transaction distribuée 2] Processus de démarrage côté client

Cet article analyse le processus de démarrage du client en mode AT du point de vue du code source. Le soi-disant client est le côté application métier. La transaction distribuée est divisée en trois modules: TC, TM, RM. TC est situé du côté seata-server, tandis que TM et RM s'exécutent du côté client via le SDK.

La figure suivante montre un scénario de transaction distribuée de la démonstration officielle de Seata, qui est divisée en les microservices suivants, qui mettent en œuvre conjointement une transaction distribuée consistant à passer des commandes, à déduire les stocks et à déduire le solde.

  • BusinessService:  service aux entreprises, l'entrée du service de commande
  • StorageService: microservice d'  inventaire, utilisé pour déduire l'inventaire des produits
  • OrderService:  microservice de commande, création de commande
  • AccountService: microservice de  compte, déduire le solde du compte utilisateur

Insérez la description de l'image ici

Il ressort également de la figure ci-dessus qu'en mode AT, le client Seata implémente principalement des transactions distribuées via les trois modules suivants:

  • GlobalTransactionScanner:  GlobalTransactionScanner est responsable de l'initialisation des modules TM et RM et de l'ajout d'intercepteurs à la méthode d'ajout d'annotations de transactions distribuées. Les intercepteurs sont responsables de l'ouverture, de la validation ou de l'annulation des transactions globales
  • DatasourceProxy:  DatasourceProxy ajoute l'interception à DataSource. L'intercepteur intercepte toutes les exécutions SQL et participe à l'exécution des transactions distribuées en tant que participant à la transaction RM.
  • Rpc Interceptor:  Dans l'article précédent sur l' interprétation du code source Seata de transaction distribuée, j'ai mentionné plusieurs points essentiels des transactions distribuées, dont l'un est la propagation d'instances interservices des transactions distribuées. La responsabilité de Rpc Interceptor est de répartir les transactions entre plusieurs microservices.

seata-spring-boot-starter

Il existe deux façons de faire référence au SDK de transaction distribuée seata, en s'appuyant sur seata-all ou seata-spring-boot-starter . Il est recommandé d'utiliser seata-spring-boot-starter, car le démarreur a injecté automatiquement les trois modules mentionnés ci-dessus. Ajoutez simplement la configuration correspondante et ajoutez l'annotation de transaction distribuée globale au code métier. Commençons par le code du projet seata-spring-boot-starter:

La figure suivante montre la structure du projet de seata-spring-boot-starter: Insérez la description de l'image ici 

Principalement divisé dans les modules suivants:

  • properties: Le  répertoire de propriétés est toutes les classes de configuration associées que Springboot adapte à seata, c'est-à-dire que les paramètres liés à Seata sont accessibles via la configuration SpringBoot
  • provider:  Les classes du répertoire du fournisseur sont chargées d'adapter la configuration de Springboot et SpringCloud à la configuration Seata
  • resources:  il existe deux fichiers principaux dans le répertoire des ressources, spring.factories est utilisé pour enregistrer les classes d'assemblage automatique Springboot et ExtConfigurationProvider est utilisé pour enregistrer les classes SpringbootConfigurationProvider. La classe Provider est responsable de l'adaptation des classes de configuration liées à SpringBoot à Seata.

Pour le projet springboot-starter, nous vérifions d'abord le fichier resources / META-INF / spring.factories:

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

Vous pouvez voir que la classe d'assemblage automatique est configurée dans spring.factories: SeataAutoConfiguration . Deux instances de GlobalTransactionScanner et de seataAutoDataSourceProxyCreator sont principalement injectées dans la classe d'assembly . code montrer comme ci-dessous:

@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 hérite d' AutoProxyCreator . AutoProxyCreator est un moyen d'implémenter AOP dans Spring. Il peut intercepter toutes les instances de Spring et déterminer si un proxy est nécessaire. La liste suivante répertorie certains des champs les plus importants de GlobalTransactionScanner et les principales méthodes d'interception des 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};
  }
}

Ce qui précède présente la manière dont GlobalTransactionScanner intercepte les transactions globales via des annotations . Les intercepteurs spécifiques sont implémentés comme TccActionInterceptor et GlobalTransactionalInterceptor. Pour le mode AT , nous nous intéressons principalement à GlobalTransactionalInterceptor . Dans les articles suivants, nous présenterons l'implémentation spécifique de GlobalTransactionalInterceptor.

De plus, GloabalTransactionScanner est également responsable de l'initialisation de TM et RM , qui est implémentée dans la méthode initClient:

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

 }

TMClient et RMClient sont tous deux des classes clientes du framework Rpc implémenté par Seata basé sur Netty , mais la logique métier est différente. Puisque TMClient est relativement plus simple, prenons RMClient comme exemple pour regarder le code source:

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();
  }
}

L'implémentation de RmNettyRemotingClient est la suivante:

@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);
  }
}

La logique ci-dessus semble plus compliquée et il existe de nombreuses classes associées, telles que: divers processeurs, divers MessageType, TransactionMessageHandler, ResourceManager. En fait, il s'agit essentiellement d'un appel Rpc, qui est divisé en appel actif Rm et appel actif Seata .

  • Rm appelle activement des méthodes:  telles que: enregistrer la branche, signaler l'état de la branche, demander un verrouillage global, etc. Les méthodes que Rm appelle activement doivent traiter la réponse renvoyée par Seata-Server dans ClientOnResponseProcessor.
  • Seata-Server appelle activement des méthodes:  telles que: validation de transaction de branche, restauration de transaction de branche, suppression du journal undolog. Seata-Server appelle activement la méthode, le côté client correspond aux différents processeurs à traiter, et une fois le traitement terminé, il revient au résultat du traitement Seata-Server Réponse. La logique d'implémentation principale de la validation et de la restauration de transaction se trouve dans TransactionMessageHandler et ResourceManager.

L'implémentation spécifique de TransactionMessageHandler et ResourceManager sera également décrite en détail dans les chapitres suivants.

Le prochain article présentera comment SeataAutoDataSourceProxyCreator et Rpc Interceptor sont initialisés et interceptés.

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

● La meilleure optimisation des performances Tomcat8 de l'histoire

Pourquoi Alibaba peut-il résister à 10 milliards en 90 secondes? - L'évolution de l'architecture distribuée à haute concurrence côté serveur

Plateforme de commerce électronique B2B - Fonction de paiement électronique ChinaPay UnionPay

Apprenez le verrouillage distribué de Zookeeper, laissez les enquêteurs vous regarder avec admiration

SpringCloud e-commerce spike microservice-solution de verrouillage distribué Redisson

Découvrez d'autres bons articles, entrez dans le compte officiel - s'il vous plaît moi - excellent dans le passé

Un compte public profond et émouvant 0.0

Je suppose que tu aimes

Origine blog.csdn.net/a1036645146/article/details/108800319
conseillé
Classement