[Interpretación del código fuente 2 de la transacción distribuida Seata] Proceso de inicio del lado del cliente

Este artículo analiza el proceso de inicio del cliente en el modo AT desde el punto de vista del código fuente El llamado cliente es el lado de la aplicación empresarial. La transacción distribuida se divide en tres módulos: TC, TM, RM. TC se encuentra en el lado del servidor seata, mientras que TM y RM se ejecutan en el lado del cliente a través de SDK.

La siguiente figura muestra un escenario de transacción distribuida de la demostración oficial de Seata, que se divide en los siguientes microservicios, que implementan conjuntamente una transacción distribuida de realizar pedidos, deducir inventario y deducir saldo.

  • BusinessService:  servicio comercial, la entrada al servicio de pedidos
  • StorageService: microservicio de  inventario, que se utiliza para deducir el inventario de productos básicos
  • OrderService:  pedir microservicio, crear orden
  • AccountService: microservicio de  cuenta, deducir el saldo de la cuenta de usuario

Inserte la descripción de la imagen aquí

También se puede ver en la figura anterior que en el modo AT, Seata Client implementa principalmente transacciones distribuidas a través de los siguientes tres módulos:

  • GlobalTransactionScanner:  GlobalTransactionScanner es responsable de inicializar los módulos TM y RM, y agregar interceptores al método de agregar anotaciones de transacciones distribuidas. Los interceptores son responsables de abrir, confirmar o revertir transacciones globales
  • DatasourceProxy:  DatasourceProxy agrega interceptación a DataSource. El interceptor intercepta toda la ejecución de SQL y participa en la ejecución de transacciones distribuidas como participante en la transacción de RM.
  • Rpc Interceptor:  En el artículo anterior sobre Interpretación del código fuente de Seata de transacciones distribuidas, mencioné varios puntos centrales de transacciones distribuidas, uno de los cuales es la propagación de instancias de servicios cruzados de transacciones distribuidas. La responsabilidad del Rpc Interceptor es distribuir transacciones entre múltiples microservicios.

seata-spring-boot-starter

Hay dos formas de referirse al SDK de transacciones distribuidas de seata, confiando en seata-all o seata-spring-boot-starter . Se recomienda usar seata-spring-boot-starter, porque el motor de arranque ha inyectado automáticamente los tres módulos mencionados anteriormente. Simplemente agregue la configuración correspondiente y agregue la anotación de transacción distribuida global al código comercial. Comencemos con el código del proyecto seata-spring-boot-starter:

La siguiente figura muestra la estructura del proyecto de seata-spring-boot-starter: Inserte la descripción de la imagen aquí 

Principalmente dividido en los siguientes módulos:

  • propiedades: El  directorio de propiedades son todas las clases de configuración relacionadas que Springboot adapta a seata, es decir, se puede acceder a los parámetros relacionados con Seata a través de la configuración de SpringBoot
  • proveedor: las  clases en el directorio de proveedores son responsables de adaptar la configuración de Springboot y SpringCloud a la configuración de Seata
  • resources:  Hay dos archivos principales en el directorio de recursos, spring.factories se usa para registrar clases de autoensamblaje Springboot y ExtConfigurationProvider se usa para registrar clases SpringbootConfigurationProvider. La clase Provider es responsable de adaptar las clases de configuración relacionadas con SpringBoot a Seata.

Para el proyecto springboot-starter, primero verificamos el archivo resources / META-INF / spring.factories:

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

Puede ver que la clase de ensamblaje automático está configurada en spring.factories: SeataAutoConfiguration . Dos instancias de GlobalTransactionScanner y seataAutoDataSourceProxyCreator se inyectan principalmente en la clase de ensamblaje . el código se muestra a continuación:

@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 hereda de AutoProxyCreator . AutoProxyCreator es una forma de implementar AOP en Spring. Puede interceptar todas las instancias en Spring y determinar si se necesita un proxy. A continuación, se enumeran algunos de los campos más importantes de GlobalTransactionScanner y los métodos principales para interceptar agentes:

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

Lo anterior presenta cómo GlobalTransactionScanner intercepta transacciones globales a través de anotaciones . Los interceptores específicos se implementan como TccActionInterceptor y GlobalTransactionalInterceptor. Para el modo AT , nos preocupamos principalmente por GlobalTransactionalInterceptor . En artículos posteriores, presentaremos la implementación específica de GlobalTransactionalInterceptor.

Además, GloabalTransactionScanner también es responsable de la inicialización de TM y RM , que se implementa en el método initClient:

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

 }

TMClient y RMClient son clases de cliente del marco Rpc implementado por Seata basado en Netty , pero la lógica comercial es diferente. Dado que TMClient es relativamente más simple, tomemos RMClient como ejemplo para ver el código fuente:

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

La implementación de RmNettyRemotingClient es la siguiente:

@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 lógica anterior parece más complicada y hay muchas clases relacionadas, como: varios procesadores, varios tipos de mensajes, TransactionMessageHandler, ResourceManager. De hecho, es esencialmente una llamada Rpc, que se divide en llamada activa Rm y llamada activa Seata .

  • Rm llama activamente a métodos:  tales como: registrar sucursal, informar sobre el estado de la sucursal, solicitar bloqueo global, etc. Los métodos a los que Rm llama activamente necesitan procesar la Respuesta devuelta por Seata-Server en ClientOnResponseProcessor.
  • Seata-Server llama activamente a métodos:  como: confirmar la transacción de la sucursal, deshacer la transacción de la sucursal, eliminar el registro de registro. Seata-Server llama activamente al método, el lado del cliente corresponde a diferentes procesadores para procesar, y una vez que se completa el procesamiento, volverá a la Respuesta del resultado del procesamiento de Seata-Server. La lógica de implementación central de la confirmación y la reversión de transacciones se encuentran en TransactionMessageHandler y ResourceManager.

La implementación específica de TransactionMessageHandler y ResourceManager también se describirá en detalle en los capítulos siguientes.

El siguiente artículo presentará cómo se inicializan e interceptan SeataAutoDataSourceProxyCreator y Rpc Interceptor.

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

● La optimización de rendimiento de Tomcat8 más sólida de la historia

¿Por qué Alibaba puede resistir 10 mil millones en 90 segundos? - La evolución de la arquitectura distribuida de alta concurrencia del lado del servidor

Plataforma de comercio electrónico B2B: función de pago electrónico ChinaPay UnionPay

Aprenda el candado distribuido de Zookeeper, deje que los entrevistadores lo miren con admiración

Solución de bloqueo distribuido de Redisson con microservicio de pico de comercio electrónico de SpringCloud

Vea más artículos buenos, ingrese a la cuenta oficial, por favor, excelente en el pasado

Una cuenta pública profunda y conmovedora 0.0

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/108800319
Recomendado
Clasificación