1. 程式人生 > >spring分散式事務控制

spring分散式事務控制

應用場景
問題描述
解決方法
多資料來源配置
單元測試
第一種方法:最大努力一次提交模式
第二種方法:最大努力一次提交模式 但使用ChainedTransactionManager
ChainedTransactionManager處理流程
第三種方法:最大努力一次提交模式 但使用atomikos
遺留問題
應用場景
現在有個專案,要做資料遷移,要把A庫中的 資料遷移到B庫,以後新的功能都在B庫上開發,兩個庫都是mysql的。但是很多舊的的專案 還在要使用A庫的資料,所以需要一個過渡期,在寫B庫的同時 也要保證能寫到A庫。為了保證兩個資料庫的資料完整性和一致性,只能同時操作兩個庫,並保證操作的原子性。

問題描述
我們要保證多資料來源 操作的原子性,就要使用分散式事物。幸好兩個資料來源都是同類型 mysql 庫。不同型別的如 mysql 和 redis 之前同步要複雜點,這個後續給出解決方法。
使用spring 事物管理機制解決分散式事物問題。可以參考博文:http://www.open-open.com/lib/view/open1429863503010.html#articleHeader8。解決方案主要包括兩大類:
(1)XA方式
(2)非XA方式
(3)用訊息佇列消除分散式事務
使用XA方式效率較低,使用訊息佇列消除分散式式事務又太過複雜。基於效率 和時間成本考慮,我選用spring的鏈式事務管理器 非XA方式 ChainedTransactionManager。它是最大努力一階段提交模式中,一個粗糙的事務管理器實現僅僅是將一系列其他的事務管理器連結在一起,去實現事務同步。倘若業務處理成功,所有的事務將會提交, 否則它們都能回滾。最大努力一次提交模式的安全性不如XA事務但也是相當不錯,因此能夠承受風險獲得較高的吞吐量收益。如果我們將關鍵業務處理服務設計為一個幕等式 (idempotent),這樣發生錯誤的可能性也很小。

解決方法
多資料來源配置
(1)資料來源配置檔案:

spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary
spring.datasource.primary.username=root
spring.datasource.primary.password=
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary
spring.datasource.secondary.username=root
spring.datasource.secondary.password=
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver

(2)spring boot載入資料來源
以下配置了兩個資料來源,但是沒有配置事務管理。

@Configuration
public class DataSourceConfig {

@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "dataSource")
@Qualifier("dataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource dataSource(www.089188.cn/) {
return DataSourceBuilder.create().build();
}

@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource"www.dfgj157.com) DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {

(3)測試用例
我沒有使用單元測試,因為單元測試裡面如果加上了@Transactional 會自動回滾事務,需要在單元測試上面加上 @Rollback(false),但是這樣就失去了測試的意義,我們就是要測試事務的原子性,不能手動設定事務的回滾方式。所以單獨寫了一個 Rest 介面用於除錯如下:

@RestController
@RequestMapping(value = "/user")
public class UserController {

@Autowired
PrimaryUserService primaryUserService;

@Autowired
SecondaryUserService secondaryUserService;

@Autowired
@Qualifier("primaryJdbcTemplate")
JdbcTemplate primaryJdbcTemplate;

@Autowired
@Qualifier("secondaryJdbcTemplate")
JdbcTemplate secondaryJdbcTemplate;


@RequestMapping(value =www.leyou1178.cn/ "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count == 0);//會拋異常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
//用業務層封裝下
//int count = primaryUserService.create(name);
//Assert.isTrue(count == 0);//會拋異常
//secondaryUserService.create(name);

33
單元測試
第一種方法:最大努力一次提交模式
但是不使用ChainedTransactionManager
(1)正常情況沒有問題 ,都能寫入資料。

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
return name;


(2)如果出現異常,資料不能保持一致了。如下所示。雖然把他們用spring的事務註解放到了一起,但是當secondaryJdbcTemplate執行完並報錯後,primaryJdbcTemplate就無法正常新增資料,但是secondaryJdbcTemplate卻並不受影響。

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count ==www.dfgjpt.com 0);//會拋異常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
return name;

備註:事實上上面兩個操作根本不受 @Transactional 註解的影響。因為單元測試裡面如果加上了@Transactional 會自動回滾事務,需要在單元測試上面加上 @Rollback(false),但是上面的操作完全沒有要回滾的意識。

第二種方法:最大努力一次提交模式 但使用ChainedTransactionManager
首先在spring boot 配置中新增如下內容:

@Bean
@Primary
public ChainedTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
DataSourceTransactionManager primaryTransactionManager = new DataSourceTransactionManager(primaryDataSource);
DataSourceTransactionManager secondaryTransactionManager = new DataSourceTransactionManager(
secondaryDataSource);
ChainedTransactionManager chainedTransactionManager = new ChainedTransactionManager(primaryTransactionManager,
secondaryTransactionManager);

(1) 正常情況沒有問題 ,都能寫入資料。如下:


(2) 如果spring業務層出現異常,資料還能保持一致

@RequestMapping(value = "add/{name}", method = RequestMethod.POST)
@ResponseBody
@Transactional
public String addUser(@PathVariable String name) {
int count = secondaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
Assert.isTrue(count == 0);//會拋異常
primaryJdbcTemplate.update("insert into USER(NAME) values(?)", name);
//用業務層封裝下
//int count = primaryUserService.create(name);
//Assert.isTrue(count ==www.tianzunyule178.com 0);//會拋異常
//secondaryUserService.create(name);
return name;


ChainedTransactionManager處理流程
ChainedTransactionManager只是同時開啟兩個事務,自動的對兩個事務,同時開啟,同時提交(或回滾)。它對多個數據寫資料,並不是一個原子操作,不能像XA那樣保證對資料操作的完整性,一致性。
看原始碼:它用一個列表來管理多個數據源的事務管理器。


用MultiTransactionStatus封裝多個數據源的事務,統一管理。


逐個Commit 事務的時候,一旦發現某個事務不能commit,立刻回滾後面的事務。

 

最後盡最大努力回滾事務列表