How does seata Client communicate with SeATA

The picture below is the basis of everything.

  1. Seata Server registers with the registry
  2. Seata Client wants to register with the registry
  3. The SeATA client finds the Seata Server based on the address and port of the Seata Server returned by the registry

Thus, the role of the registry line in this scenario is to let the client find the server

Let’s look at registries. There are two types of registries, file and non-file. Let’s look at the file type first

Registry of type file (configuration of type file)

The file type is a registry used for proof of concept. Its goal is to let clients find Seata Server directly through the configuration file, so that they don’t have to rely on third party registries for quick validation.

The core configuration points are as follows

. #transaction Service group mapping #transaction service group mapping #transaction service group mapping #transaction service group mapping #transaction service group mapping If the registry is NACOS, the corresponding configuration items need to be configured in NACOS, which uses file configuration directly. vgroupMapping.my_test_tx_group = "default" #only support when registry.type=file, Please don't set multiple addresses # Only use registry. Type =file in registry. Conf to specify where seata server is located. Default. Grouplist = "127.0.0.1:8091" # degrade, current not support enableDegrade = false #disable seata disableGlobalTransaction = false ... Omit some code}Copy the code

The relationship between the SeATA Client and the Seata Server is shown in the following figure

## transaction log store, only used in seata-server store { ## store mode: {## store location dir = "sessionStore" # branch session size, if exceeded first try compress lockkey, still exceeded throws exceptions maxBranchSessionSize = 16384 # globe session size , if exceeded throws exceptions maxGlobalSessionSize = 512 # file buffer size , if exceeded allocate new buffer fileWriteBufferCacheSize = 16384 # when recover batch read size sessionReloadReadSize = 100 # async, sync flushDiskMode = async } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. dbType = "Mysql" driverClassName = ". Com. Mysql. JDBC Driver "url =" JDBC: mysql: / / 127.0.0.1:3306 / seata "user =" mysql "password = "mysql" minConn = 1 maxConn = 10 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 } }Copy the code

Nacos-style registry

Registry. Conf file for seata-server

Registry {# file, nacos, Eureka, Redis, ZK, Consul, ETCD3, SOFA type = "nacos" nacos {application = "seata-server" ServerAddr = "localhost" Namespace = "" cluster = "default" username = "nacos" Password = "nacOS"}} NacOS Part of the configuration center Config {# file, nacos, Apollo, zk, consul, etCD3 type = "nacos" nacos {serverAddr = "localhost" namespace = "" group = "SEATA_GROUP" # This configuration is important. The configuration item on the server must belong to the specified group. Username = "nacos" password = "nacos"}}}Copy the code

The client can specify the configuration center in different ways. The original way is to use Registry. Conf. You can specify the contents of the registry. Conf file on the client side through the Spring application.yml (or application.properties) file

Registry {# file, nacos, Eureka, redis, ZK, Consul, ETCD3, SOFA type = "nacos" nacos {serverAddr = "localhost" namespace = "" cluster = "default" username = "nacos" password = "nacos"}} config {# file, nacos, Apollo, Zk, consul, etCD3 type = "Nacos" nacOS {serverAddr =" localhost" Namespace =" "group="SEATA_GROUP" Username =" nacos" password = "nacos" } }Copy the code

Spring-cloud-alibaba client configuration mode

# ---------- Configuration center, if you do not need to use configuration center, Can delete this part configuration -- -- -- -- -- -- -- -- -- -- setting configuration center server address # # spring. Cloud. Nacos. Config. The server - addr = 127.0.0.1:8848 # nacos authentication information spring.cloud.nacos.config.username=nacos spring.cloud.nacos.config.password=nacos Spring. Cloud. Nacos. Config. ContextPath = / nacos # set the registry server address spring. Cloud. Nacos. Discovery. The server - addr = 127.0.0.1:8848 # Nacos authentication information spring. Cloud. Nacos. Discovery. The username = nacos spring. Cloud. Nacos. Discovery. Password = nacosCopy the code

Seata client internals

Client internal rollback mechanism

According to the official documentation, SEATA analyzes THE SQL that modifies the data and generates the corresponding reverse rollback SQL, which is recorded in the undo_log table. Therefore, each client must have a corresponding undo_log table, as defined below

CREATE TABLE `undo_log`
(
  `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT,
  `branch_id`     BIGINT(20)   NOT NULL,
  `xid`           VARCHAR(100) NOT NULL,
  `context`       VARCHAR(128) NOT NULL,
  `rollback_info` LONGBLOB     NOT NULL,
  `log_status`    INT(11)      NOT NULL,
  `log_created`   DATETIME     NOT NULL,
  `log_modified`  DATETIME     NOT NULL,
  `ext`           VARCHAR(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;
Copy the code

So where does the generated rollback SQL for analysis occur? The answer is in the DataSource. Let’s look at how the Seata Client works.

All of the core in the IO. Seata. Rm. The datasource. DataSourceProxy

DataSourceProxy(java.sql.Datasource) is used as the include relation, and DataSourceProxy’s parent class AbstractDataSourceProxy implements the Datasource interface. So DataSourceProxy is also an implementation of Datasource, which gives DataSourceProxy the opportunity to analyze the SQL to be executed and generate the corresponding rollback SQL.

DataSourceProxy is the default java.sql.Datasource implementation. Mybatis (jdbcTemplate) is the default implementation for DataSourceProxy. So all the configuration on the client side is around registering the DataSourceProxy as the default java.sql.Datasource, or configuring the DataSourceProxy as the Datasource of the database access framework.

After understanding the above ideas, what we did during development is to complete the configuration of DataSourceProxy.

Starting with seata0.9, DataSource automatic proxy is available and enabled by default. So what does this operation mean? Just so you know, you don’t have to worry about adding DataSource to DataSourceProxy, seATA will do it for you automatically. Then you just need to put the DataSource somewhere else where the DataSource is used.

Automatic assembly of client DataSourceProxy

How does SEATA automate assembly?

All of the core in the IO. Seata. Spring. The boot. Autoconfigure. SeataAutoConfiguration And IO. Seata. Spring. The annotation. The datasource. SeataDataSourceBeanPostProcessor

In the SeataAutoConfiguration, through seata. EnableAutoDataSourceProxy’s value to judge whether or not registered SeataDataSourceBeanPostProcessor bean.

. There is a huge pit seata enableAutoDataSourceProxy this configuration is written in the book, yaml file, smart tips configuration item is enable – auto – data – source – proxy, unable to take effect. You must write for enableAutoDataSourceProxy form.

The Settings given by intelligent prompt cannot take effect.
seata:
  enable-auto-data-source-proxy: false
# Examples of working correctly
seata:
  enableAutoDataSourceProxy: false
Copy the code

This SeataDataSourceBeanPostProcessor implements org. Springframework. Beans. Factory. Config. The BeanPostProcessor interface, BeanPostProcessor interface methods postProcessBeforeInitialization and postProcessAfterInitialization has two hooks, Then seata in postProcessAfterInitialization operation fierce like a tiger, complete the function of automatic proxy. Interested in their own look at the source code.

The BeanPostProcessor hook flow is as follows

Matters needing attention

  1. Both methods in the interface return the bean passed in, not NULL. If null is returned, we will not get the target through the getBean method.
  2. BeanFactory and ApplicationContext treat bean post-handlers slightly differently. ApplicationContext automatically detects all beans that implement the BeanPostProcessor interface in the configuration file, registers them as post-processors, and calls them when the container creates the bean, Therefore, deploying a post-processor is no different from deploying any other bean. With the BeanFactory implementation, the bean afterprocessor must be explicitly de-registered by code, which is defined in the ConfigurableBeanFactory interface in the IoC container inheritance architecture
  3. Do not mark the BeanPostProcessor as lazy initialization. Because if you do, the Spring container will not register them and the custom logic will not be applied. If you use the ‘default-lazy-init’ attribute in your element definition, make sure your individual Beanpostprocessors are marked as ‘lazy-init=”false”.

Send a bonus about the life cycle

Autoassembly Settings – all version 1.1.0 as an example

On and off

  • For using seata – spring – the boot – the starter, the default is open source automatic agent, if you need to shut down, please configure seata. EnableAutoDataSourceProxy = false, the configuration is true by default. To change the proxy implementation mode, run seata.usejdkProxy =false. The default value is false. CGLIB is used as the automatic proxy implementation mode for data sources.
  • To use the seata – all the way, please use the @ EnableAutoDataSourceProxy to explicitly open source automatic proxy function. If necessary, the proxy implementation can be switched through the useJdkProxy property of the annotation. The default value is false, and CGLIB is used as the automatic proxy of the data source.

Other versions 1.0.0: client. Support. Spring. The datasource. Autoproxy = true 0.9.0: support. Spring. The datasource. Autoproxy = true

Example of automatic configuration – Mybatis – Plus is used as an example


@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource(a) {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources("classpath*:/mapper/*.xml"));
        returnfactoryBean.getObject(); }}Copy the code

application.yaml

spring:
  application:
    name: alicloudapp
  datasource:
    name: storageDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 2
      max-active: 20
      min-idle: 2
      url: 'jdbc:mysql://localhost:3306/seata_storage? characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true'
      username: root
      password: openstack
      driver-class-name: 'com.mysql.cj.jdbc.Driver'
      name: storageDataSource
Copy the code

Example of automatic configuration disabled – All is mybatis- Plus

application.yaml

#seata use has several points
# 1 custom configuration data source see SeataConfiguration, eliminate DataSourceAutoConfiguration at the same time
# 2 configuration micro service seata registered name, by the spring. Cloud. Alibaba. Seata. Tx - service - group = < service - group - the name > to the configuration, the configuration of needs and nacos configuration.
Data_ID=serivce. Vgroup_mapp.
      
       , group =SEATA_GROUP in the text format. For the configuration item whose configuration content is default, see nacos-seata-configuration. PNG
      
#4 Configure type in registry. Conf to use NACOS as the service discovery and configuration center
SEATA_GROUP = SEATA_GROUP = SEATA_GROUP = SEATA_GROUP = SEATA_GROUP = SEATA_GROUP = SEATA_GROUP = SEATA_GROUP
#6 In the case of spring-cloud-starter-Alibaba-seata, the version of client and server should be the same.
#7 In seATA, auto datasource Proxy will be implemented in 0.9. You need to select custom assembly ProxyDataSource or auto datasource
spring:
  autoconfigure:
DataSource/ProxyDataSource/DataSource/DataSource/DataSource/DataSource
    exclude: [org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,com.alibaba.druid.spring.boot.autoconfigure.Dru idDataSourceAutoConfigure]
  profiles: seata
  cloud:
    alibaba:
      seata:
        tx-service-group: storage-service-group
[SeataAutoConfiguration] [SeataAutoConfiguration] [SeataAutoConfiguration] [SeataAutoConfiguration
seata:
  enableAutoDataSourceProxy: false
Copy the code

MybatisPlusConfiguration.java

@Configuration
@MapperScan(basePackages = {"com.mycompany.alicloudapp.mapper"})
@Slf4j
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MybatisPlusConfiguration {

    @Profile("dev")
    @Bean
    public PerformanceMonitorInterceptor performanceMonitorInterceptor(a) {
        return new PerformanceMonitorInterceptor();
    }

    /** ** ** ** ** ** ** ** ** ** ** **@return* /
    @Bean
    public PaginationInterceptor paginationInterceptor(a) {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setCountSqlParser(new JsqlParserCountOptimize(true));

        return page;
    }

    / * * *@param sqlSessionFactory SqlSessionFactory
     * @return SqlSessionTemplate
     */
    @Autowired(required = true)
    private DataSourceProperties dataSourceProperties;

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /** * / druid/druid/druid/druid/druid/druid/druid/druid/druid@return* /
    @Bean(name = "datasource")
    @Primary
    public DataSourceProxy druidDataSource(a) {

        DruidDataSource druidDataSource = new DruidDataSource();
        log.info("dataSourceProperties.getUrl():{}", dataSourceProperties);
        druidDataSource.setUrl(dataSourceProperties.getUrl());
        druidDataSource.setUsername(dataSourceProperties.getUsername());
        druidDataSource.setPassword(dataSourceProperties.getPassword());
        druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
        druidDataSource.setInitialSize(1);
        druidDataSource.setMaxActive(120);
        druidDataSource.setMaxWait(60000);
        druidDataSource.setMinIdle(1);
        druidDataSource.setValidationQuery("Select 1 from DUAL");
        druidDataSource.setTestOnBorrow(false);
        druidDataSource.setTestOnReturn(false);
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
        druidDataSource.setMinEvictableIdleTimeMillis(25200000);
        druidDataSource.setRemoveAbandoned(true);
        druidDataSource.setRemoveAbandonedTimeout(1800);
        druidDataSource.setLogAbandoned(true);
        log.info("Loading the dataSource...");
        return new DataSourceProxy(druidDataSource);
    }


    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
// DataSource dataSourceProxy = new DataSourceProxy(druidDataSource);

        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSourceProxy);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // bean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
        bean.setMapperLocations(resolver.getResources("classpath*:/com.mycompany.alicloudapp.mapper/**/*.xml"));
        SqlSessionFactory factory = null;
        try {
            factory = bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        returnfactory; }}Copy the code

And the more concise way to write it is

@Configuration @ConditionalOnClass(DruidDataSource.class) @AutoConfigureBefore(DataSourceAutoConfiguration.class) public  class MyDataSourceConfig { @Bean @ConfigurationProperties(prefix ="spring.datasource.druid")
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        returnnew DataSourceProxy(druidDataSource); }}Copy the code

At this point, the relevant parts of the DataSourceProxy are configured