( 十六 )、SpringBoot 多數據源分布式事務 之 Spring-boot-starter-jta-atomikos
1、簡介
這種情況適用於在一個項目中但是有多個數據源的情況,如果是微服務的分布式事務則不建議用這種方式,主要是因為這種方式是阻塞的。
XA 事務的基礎是兩階段提交協議。分為以下兩階段:
- 需要有一個事務協調者來保證所有的事務參與者都完成了准備工作。
- 如果協調者收到所有參與者都准備好的消息,就會通知所有的事務都可以提交了。
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); } }