( 十六 )、SpringBoot 多数据源分布式事务 之 Spring-boot-starter-jta-atomikos

( 十六 )、SpringBoot 多数据源分布式事务 之 Spring-boot-starter-jta-atomikos

 

 

1、简介

 这种情况适用于在一个项目中但是有多个数据源的情况,如果是微服务的分布式事务则不建议用这种方式,主要是因为这种方式是阻塞的。

XA 事务的基础是两阶段提交协议。分为以下两阶段:

  1. 需要有一个事务协调者来保证所有的事务参与者都完成了准备工作。
  2. 如果协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了。

 Mysql 在这个XA事务中扮演的是参与者的角色,而不是协调者(事务管理器)。

第一阶段(准备阶段):
协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写redo或者undo日志,让后锁定资源,执行操作,但并不提交。
第二阶段:
如果每个参与者明确返回准备成功,则协调者向参与者发送提交指令,参与者释放锁定的资源,如何任何一个参与者明确返回准备失败,则协调者会发送中指指令,参与者取消已经变更的事务,释放锁定的资源。

两阶段提交方案应用非常广泛,几乎所有商业OLTP数据库都支持XA协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。

缺点:如果协调者宕机,参与者没有协调者指挥,则会一直阻塞。

三阶段提交协议
三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题,并且把两个阶段增加为三个阶段:

询问阶段:协调者询问参与者是否可以完成指令,协调者只需要回答是还是不是,而不需要做真正的操作,这个阶段超时导致中止。

准备阶段:如果在询问阶段所有的参与者都返回可以执行操作,协调者向参与者发送预执行请求,然后参与者写redo和undo日志,执行操作,但是不提交操作;如果在询问阶段任何参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的的准备阶段是相似的,这个阶段超时导致成功

提交阶段:如果每个参与者在准备阶段返回准备成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者返回准备失败,也就是预留资源或者执行操作失败,协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致

 2PC与3PC提交区别:
三阶段提交协议与两阶段提交协议相比,优点:增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大

但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞和永远锁定资源。

2、maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

 

3、Yml配置 数据源

mysql:
  datasource:
    db1:
      url: jdbc:mysql://localhost:3306/test01
      username: root
      password: 123456
      driverClassName: com.mysql.cj.jdbc.Driver
      minPoolSize: 3
      maxPoolSize: 25
      maxLifetime: 20000
      mborrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60
    db2:
      url: jdbc:mysql://localhost:3306/test02
      username: root
      password: 123456
      driverClassName: com.mysql.cj.jdbc.Driver
      minPoolSize: 3
      maxPoolSize: 25
      maxLifetime: 20000
      mborrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60

 

4、读取配置到Bean

DB1Config、DB2Config

@ConfigurationProperties(prefix = "mysql.datasource.db1")
@Component
public class DB1Config {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String driverClassName;
    private String testQuery;
}

@ConfigurationProperties(prefix = "mysql.datasource.db2")
@Component
public class DB2Config {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String driverClassName;
    private String testQuery;
}

 

5、数据源配置

DB1MybatisConfig、DB2MybatisConfig 
@Configuration
@MapperScan(basePackages = "com.dw.study.mapper.db1", sqlSessionTemplateRef = "db1SessionTemplate")
public class DB1MybatisConfig {

    @Autowired
    private DB1Config db1Config;

    @Bean("db1DataSource")
    public DataSource db1DataSource() throws SQLException {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl(db1Config.getUrl());
        druidXADataSource.setPassword(db1Config.getPassword());
        druidXADataSource.setUsername(db1Config.getUsername());
        druidXADataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName("db1DataSource");
        xaDataSource.setMinPoolSize(db1Config.getMinPoolSize());
        xaDataSource.setMaxPoolSize(db1Config.getMaxPoolSize());
        xaDataSource.setMaxLifetime(db1Config.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(db1Config.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(db1Config.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(db1Config.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(db1Config.getMaxIdleTime());
        xaDataSource.setXaDataSource(druidXADataSource);
        return xaDataSource;
    }

    @Bean(name = "db1SimpleSessionFactory")
    public SqlSessionFactory db1SimpleSessionFactory(@Qualifier("db1DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resourceResolver.getResources("classpath:mapper/db1/*.xml"));
        bean.setTypeAliasesPackage("com.dw.study.pojo.*");
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        bean.setConfiguration(configuration);
        return bean.getObject();
    }

    @Bean(name = "db1SessionTemplate")
    public SqlSessionTemplate db1SessionTemplate(
            @Qualifier("db1SimpleSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}




@Configuration
@MapperScan(basePackages = "com.dw.study.mapper.db2",sqlSessionTemplateRef = "db2SessionTemplate")
public class DB2MybatisConfig {

    @Autowired
    private DB2Config DB2Config;

    @Bean("db2DataSource")
    public DataSource db2DataSource() throws SQLException {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl(DB2Config.getUrl());
        druidXADataSource.setPassword(DB2Config.getPassword());
        druidXADataSource.setUsername(DB2Config.getUsername());
        druidXADataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName("db2DataSource");
        xaDataSource.setMinPoolSize(DB2Config.getMinPoolSize());
        xaDataSource.setMaxPoolSize(DB2Config.getMaxPoolSize());
        xaDataSource.setMaxLifetime(DB2Config.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(DB2Config.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(DB2Config.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(DB2Config.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(DB2Config.getMaxIdleTime());
        xaDataSource.setXaDataSource(druidXADataSource);
        return xaDataSource;
    }

    @Bean(name = "db2SessionFactory")
    public SqlSessionFactory lyjSessionFactory(@Qualifier("db2DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resourceResolver.getResources("classpath:mapper/db2/*.xml"));
        bean.setTypeAliasesPackage("com.dw.study.pojo.*");
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        bean.setConfiguration(configuration);
        return bean.getObject();
    }

    @Bean(name = "db2SessionTemplate")
    public SqlSessionTemplate db2SessionTemplate(
            @Qualifier("db2SessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

 

posted @ 2021-11-25 23:08  邓维-java  阅读(951)  评论(0编辑  收藏  举报