Инвентаризация Seata: процесс настройки клиента

задняя часть
Инвентаризация Seata: процесс настройки клиента

Это 13-й день, когда я участвую в Gengwen Challenge. Проверьте подробности мероприятия:Обновить вызов

Прежде всего, поделитесь всеми предыдущими статьями, ставьте лайки, добавляйте в избранное и пересылайте три раза подряд. >>>>😜😜😜
Сборник статей:🎁nuggets.capable/post/694164…
Github :👉github.com/black-ant

Введение

В этой статье давайте рассмотрим файл конфигурации клиента Seaate, а также метод и процесс настройки.Сначала рассмотрим конфигурацию:

#====================================Seata Config===============================================
seata:
  enabled: true
  application-id: business-seata-example
  tx-service-group: business-service-seata-service-group # 事务群组(可以每个应用独立取名,也可以使用相同的名字)
  client:
    rm-report-success-enable: true
    rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false)
    rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5)
    rm-async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000)
    rm:
      lock:
        lock-retry-internal: 10 # 校验或占用全局锁重试间隔(默认10ms)
        lock-retry-times:    30 # 校验或占用全局锁重试次数(默认30)
        lock-retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略(优先释放本地锁让回滚成功)
    tm-commit-retry-count:   3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1)
    tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1)
    undo:
      undo-data-validation: true # 二阶段回滚镜像校验(默认true开启)
      undo-log-serialization: jackson # undo序列化方式(默认jackson)
      undo-log-table: undo_log  # 自定义undo表名(默认undo_log)
    log:
      exceptionRate: 100 # 日志异常输出概率(默认100)
    support:
      spring:
        datasource-autoproxy: true
  service:
    vgroup-mapping:
      my_test_tx_group: default # TC 集群(必须与seata-server保持一致)
    enable-degrade: false # 降级开关
    disable-global-transaction: false # 禁用全局事务(默认false)
    grouplist:
      default: 127.0.0.1:8091
  transport:
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      share-boss-worker: false
      client-selector-thread-prefix: NettyClientSelector
      client-selector-thread-size: 1
      client-worker-thread-prefix: NettyClientWorkerThread
    type: TCP
    server: NIO
    heartbeat: true
    serialization: seata
    compressor: none
    enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true)
  registry:
    file:
      name: file.conf
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace:
      cluster: default
  config:
    file:
      name: file.conf
    type: nacos
    nacos:
      namespace:
      server-addr: localhost:8848

2. Объект конфигурации

2.1 Объекты верхнего уровня Seata

Конфигурация seata.xxx, соответствующая объекту

@ConfigurationProperties(prefix = "seata")
@EnableConfigurationProperties(SpringCloudAlibabaConfiguration.class)
public class SeataProperties {
    /**
     * 是否启用自动配置
     */
    private boolean enabled = true;
    /**
     * application id
     */
    private String applicationId;
    /**
     * 事务服务组
     */
    private String txServiceGroup;
    /**
     * 是否启用数据源bean的自动代理
     */
    private boolean enableAutoDataSourceProxy = true;
    /**
     * 数据源代理模式
     */
    private String dataSourceProxyMode = DefaultValues.DEFAULT_DATA_SOURCE_PROXY_MODE;
    /**
     * 是否使用JDK代理而不是CGLIB代理
     */
    private boolean useJdkProxy = false;
    /**
     * 指定哪个数据源bean不符合自动代理的条件
     */
    private String[] excludesForAutoProxying = {};
    
}

2.2 объект конфигурации seata.client

 client:
    rm-report-success-enable: true
    rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false)
    rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5)
    tm-commit-retry-count:   3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1)
    tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1)
    
// 一级目录下有如下配置 , 对应的对象为 : 
public class RmProperties {
    private int asyncCommitBufferLimit = 10000;
    private int reportRetryCount = 5;
    private boolean tableMetaCheckEnable = false;
    private boolean reportSuccessEnable = false;
    private boolean sagaBranchRegisterEnable = false;
    private String sagaJsonParser = fastjson;   
}    
  

Выше приведен класс, соответствующий конфигурации первого уровня клиента, давайте взглянем на его подконфигурацию.

// 二级目录 : seata.client.log
public class LogProperties {
    private int exceptionRate = 100;
}

// 二级目录 : seata.client.undo
public class UndoProperties {
    private boolean dataValidation = true;
    private String logSerialization = "jackson";
    private String logTable = "undo_log";
    private boolean onlyCareUpdateColumns = true;
}
      
// 二级目录 : seata.client.support

2.3 объект seata.service

@Component
@ConfigurationProperties(prefix = "seata.service")
public class ServiceProperties implements InitializingBean {
    /**
     * vgroup->rgroup
     */
    private Map<String, String> vgroupMapping = new HashMap<>();
    /**
     * group list
     */
    private Map<String, String> grouplist = new HashMap<>();
    /**
     * degrade current not support
     */
    private boolean enableDegrade = false;
    /**
     * disable globalTransaction
     */
    private boolean disableGlobalTransaction = false;

}

2.4 seata.transport

Давайте посмотрим на классы конфигурации и параметры по умолчанию.

@Component
@ConfigurationProperties(prefix = TRANSPORT_PREFIX)
public class TransportProperties {
    /**
     * tcp, unix-domain-socket
     */
    private String type = "TCP";
    /**
     * NIO, NATIVE
     */
    private String server = "NIO";
    /**
     * enable heartbeat
     */
    private boolean heartbeat = true;
    /**
     * serialization
     */
    private String serialization = "seata";
    /**
     * compressor
     */
    private String compressor = "none";

    /**
     * enable client batch send request
     */
    private boolean enableClientBatchSendRequest = true;
    
}


@Component
@ConfigurationProperties(prefix = "seata.transport.thread-factory")
public class ThreadFactoryProperties {
    private String bossThreadPrefix = "NettyBoss";
    private String workerThreadPrefix = "NettyServerNIOWorker";
    private String serverExecutorThreadPrefix = "NettyServerBizHandler";
    private boolean shareBossWorker = false;
    private String clientSelectorThreadPrefix = "NettyClientSelector";
    private int clientSelectorThreadSize = 1;
    private String clientWorkerThreadPrefix = "NettyClientWorkerThread";
}    

2.5 seata.config

@Component
@ConfigurationProperties(prefix = CONFIG_PREFIX)
public class ConfigProperties {
    /**
     * file, nacos, apollo, zk, consul, etcd3, springCloudConfig
     */
    private String type = "file";
    
}

ConfigNacosProperties 等 ConfigXXXProperties 

Обратите внимание, что существует несколько классов реализации, каждый из которых соответствует классу конфигурации.

image.png

2.6 seata.registry

@Component
@ConfigurationProperties(prefix = "seata.registry")
public class RegistryProperties {
    /**
     * file, nacos, eureka, redis, zk, consul, etcd3, sofa
     */
    private String type = "file";
    /**
     * the load balance
     */
    private String loadBalance = DEFAULT_LOAD_BALANCE;
    /**
     * 负载均衡虚拟节点
     */
    private int loadBalanceVirtualNodes = VIRTUAL_NODES_DEFAULT;
    
}

3. Процесс инициализации клиента

3.1 Процесс инициализации класса конфигурации

Давайте взглянем на основной класс конфигурации SeataAutoConfiguration, который здесь будет сканироваться.io.seata.spring.boot.autoconfigure.propertiesВсе классы конфигурации в рамках пакета

// Client 的配置化主要基于 SeataAutoConfiguration 类来完成 Client 配置操作

@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 {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoConfiguration.class);

    @Bean(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER)
    @ConditionalOnMissingBean(name = {BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER})
    public SpringApplicationContextProvider springApplicationContextProvider() {
        return new SpringApplicationContextProvider();
    }

    @Bean(BEAN_NAME_FAILURE_HANDLER)
    @ConditionalOnMissingBean(FailureHandler.class)
    public FailureHandler failureHandler() {
        return new DefaultFailureHandlerImpl();
    }

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

    /**
     * 数据源配置
     */
    @Configuration
    @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {"enableAutoDataSourceProxy", "enable-auto-data-source-proxy"}, havingValue = "true", matchIfMissing = true)
    static class SeataDataSourceConfiguration {

        /**
         * The bean seataDataSourceBeanPostProcessor.
         */
        @Bean(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR)
        @ConditionalOnMissingBean(SeataDataSourceBeanPostProcessor.class)
        public SeataDataSourceBeanPostProcessor seataDataSourceBeanPostProcessor(SeataProperties seataProperties) {
            return new SeataDataSourceBeanPostProcessor(seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
        }

        /**
         * The bean seataAutoDataSourceProxyCreator.
         */
        @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
        @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
        public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
            return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
                    seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
        }
    }
}

Задействованы следующие объекты:

  • C- SpringApplicationContextProvider
  • C- FailureHandler
  • C- GlobalTransactionScanner
  • C- SeataDataSourceBeanPostProcessor
  • C- SeataAutoDataSourceProxyCreator

3.1.1 SpringApplicationContextProvider

public class SpringApplicationContextProvider implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext);
    }
}

Как вы можете видеть здесь, ObjectHolder вызывается для хранения applicationContext.

Когда я ранее анализировал Spring, я сказал, что Aware можно использовать для уведомления после создания, вот параметр, установленный для класса перечисления.

Это первый раз, когда я вижу класс перечисления, используемый таким образом, реализует ли он синглтон??

public enum ObjectHolder {

    INSTANCE;
    private static final int MAP_SIZE = 8;
    private static final Map<String, Object> OBJECT_MAP = new ConcurrentHashMap<>(MAP_SIZE);

    public Object getObject(String objectKey) {
        return OBJECT_MAP.get(objectKey);
    }

    public <T> T getObject(Class<T> clasz) {
        return clasz.cast(OBJECT_MAP.values().stream().filter(clasz::isInstance).findAny().orElseThrow(() -> new ShouldNeverHappenException("Can't find any object of class " + clasz.getName())));
    }

    public Object setObject(String objectKey, Object object) {
        return OBJECT_MAP.putIfAbsent(objectKey, object);
    }
}

3.1.2 FailureHandler

В FailureHandler предусмотрены следующие методы:

public interface FailureHandler {

    // 启动错误
    void onBeginFailure(GlobalTransaction tx, Throwable cause);
    // 提交异常
    void onCommitFailure(GlobalTransaction tx, Throwable cause);
    // 回退异常
    void onRollbackFailure(GlobalTransaction tx, Throwable originalException);
    // 重试操作
    void onRollbackRetrying(GlobalTransaction tx, Throwable originalException);
}

Класс реализации по умолчанию — DefaultFailureHandlerImpl , где настраиваемый означает, что вы можете реализовать и расширить его самостоятельно.

3.1.3 GlobalTransactionScanner

В этом классе задействовано несколько абстракций и интерфейсов:

  • AbstractAutoProxyCreator : реализация BeanPostProcessor, которая обертывает bean-компоненты прокси-серверами AOP.
  • ConfigurationChangeListener : прослушиватель модификации конфигурации
  • InitializingBean: вызов инициализации
  • ApplicationContextAware : обработка слушателя с учетом
  • DisposableBean: уничтожить обработку

C- AbstractAutoProxyCreator # wrapIfNecessary

AbstractAutoProxyCreator подходит для прокси postProcess, который в основном запускается на этапе postProcessAfterInitialization, который называется:

    // 此方法用于校验
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        try {
            synchronized (PROXYED_SET) {
                if (PROXYED_SET.contains(beanName)) {
                    return bean;
                }
                interceptor = null;
                // 检测是否存在 TCC 代理
                if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
                    // 创建一个 TccActionInterceptor 拦截器 , 并且添加到监听器
                    interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
                    ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                        (ConfigurationChangeListener)interceptor);
                } else {
                    // 获取目标类class
                    Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
                    // 
                    Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
					
                    // 判断是否存在 GlobalTransactional 注解 , 不存在直接返回
                    if (!existsAnnotation(new Class[]{serviceInterface})
                        && !existsAnnotation(interfacesIfJdk)) {
                        return bean;
                    }
					
                    // 如果存在注解 , 则为其创建一个 GlobalTransactionalInterceptor 用于事务拦截
                    if (interceptor == null) {
                        if (globalTransactionalInterceptor == null) {
                            globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
                            ConfigurationCache.addConfigListener(
                                ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                                (ConfigurationChangeListener)globalTransactionalInterceptor);
                        }
                        interceptor = globalTransactionalInterceptor;
                    }
                }
                
                // 如果不是 AOP 代理 , 则直接放回 , 否者 , 进行 AOP 实际处理
                if (!AopUtils.isAopProxy(bean)) {
                    bean = super.wrapIfNecessary(bean, beanName, cacheKey);
                } else {
                    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);
        }
    }

PS: Роль AdvisedSupport

Проще говоря, этот класс проксируется.Реализация BeanPostProcessor каждого подходящего bean-компонента обернута прокси-сервером AOP, который будет делегировать указанному перехватчику перед вызовом самого bean-компонента.

ConfigurationChangeListener

@Override
public void onChangeEvent(ConfigurationChangeEvent event) {
    if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) {
        disableGlobalTransaction = Boolean.parseBoolean(event.getNewValue().trim());
        if (!disableGlobalTransaction && initialized.compareAndSet(false, true)) {
            //  可以看到 , 再修改之后会重新初始化一次客户端
            initClient();
        }
    }
}   

InitializingBean

public void afterPropertiesSet() {
	ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
            (ConfigurationChangeListener)this);
	if (disableGlobalTransaction) {
            return;
	}
	if (initialized.compareAndSet(false, true)) {
            // 同样 , 初始化的时候调用了 initClient
            initClient();
	}
}

См. здесь, что здесь инициализируются клиенты TM и RM.

private void initClient() {

    if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
            throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
    }
    //init TM
    TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);

    //init RM
    RMClient.init(applicationId, txServiceGroup);

    // 注册销毁钩子
    registerSpringShutdownHook();

}

ApplicationContextAware

Настройки уведомлений выполняются здесь через Aware ApplicationContext.

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
	this.applicationContext = applicationContext;
	this.setBeanFactory(applicationContext);
}

DisposableBean

public void destroy() {
    ShutdownHook.getInstance().destroyAll();
}

private void registerSpringShutdownHook() {
    if (applicationContext instanceof ConfigurableApplicationContext) {
        ((ConfigurableApplicationContext) applicationContext).registerShutdownHook();
        ShutdownHook.removeRuntimeShutdownHook();
    }
    ShutdownHook.getInstance().addDisposable(TmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
    ShutdownHook.getInstance().addDisposable(RmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
}

3.1.4 SeataDataSourceBeanPostProcessor

public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor {

    private static final Logger LOGGER = LoggerFactory.getLogger(SeataDataSourceBeanPostProcessor.class);

    private final List<String> excludes;
    private final BranchType dataSourceProxyMode;

    public SeataDataSourceBeanPostProcessor(String[] excludes, String dataSourceProxyMode) {
        this.excludes = Arrays.asList(excludes);
        this.dataSourceProxyMode = BranchType.XA.name().equalsIgnoreCase(dataSourceProxyMode) ? BranchType.XA : BranchType.AT;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            //当不在排除时,放置和初始化代理
            if (!excludes.contains(bean.getClass().getName())) {
                //只放置和初始化代理,不返回代理
                DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode);
            }

            //如果为SeataDataSourceProxy,则返回原始数据源
            if (bean instanceof SeataDataSourceProxy) {
                return ((SeataDataSourceProxy) bean).getTargetDataSource();
            }
        }
        return bean;
    }
}


SeataDataSourceProxy 是一个接口 , 它提供了多个方法 :
public interface SeataDataSourceProxy extends DataSource {

    // 获取目标数据源。
    DataSource getTargetDataSource();

    // 得到分支类型。
    BranchType getBranchType();
}

而该代理类 , 有如下几个实现 :

seata-system-SeataDataSourceProxy.png

SeataAutoDataSourceProxyCreator

public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class);
    private final List<String> excludes;
    private final Advisor advisor;

    public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
        this.excludes = Arrays.asList(excludes);
        this.advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode));
        setProxyTargetClass(!useJdkProxy);
    }

    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
        return new Object[]{advisor};
    }

    @Override
    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        return !DataSource.class.isAssignableFrom(beanClass) ||
            SeataProxy.class.isAssignableFrom(beanClass) ||
            excludes.contains(beanClass.getName());
    }
}

Суммировать

На самом деле, самая важная операция всей статьи — построитьglobalTransactionalInterceptor, мы будем использовать этот перехватчик в последующем основном процессе

Обратите внимание, что каждый аннотированный метод будет обрабатываться перехватчиком.