在分佈式系統中實現的事務就是分佈式事務,分佈式系統的CAP原則是:mysql
是分佈式事務主要是保證數據的一致性,主要有三種不一樣的原則git
共同點:github
由於JTA採用兩階段提交方式,第一次是預備階段,第二次纔是正式提交。當第一次提交出現錯誤,則整個事務出現回滾,一個事務的時間可能會較長,由於它要跨越多個數據庫多個數據資源的的操做,因此在性能上可能會形成吞吐量低。spring
1.start message transaction
2.receive message
3.start database transaction
4.update database
5.commit database transaction
6.commit message transaction ##當這一步出現錯誤時,上面的由於已經commit,因此不會rollback
複製代碼
這時候就會出現問題sql
1.start message transaction
2.receive message
3.start JTA transaction on DB
4.update database
5.phase-1 commit on DB transaction
6.commit message transaction ##當這一步出現錯誤時,上面的由於是XA的第一次提交預備狀態,因此能夠rollback
7.phase-2 commit on DB transaction ##當這一步出現錯誤時,由於message不是XA方式,commit後沒法rollback
複製代碼
但這種相比不使用JTA,已經很大程度上避免了事務發生錯誤的可能性。數據庫
1.start message transaction
2.receive message
3.start database transaction
4.update database #數據庫操做出錯,消息被放回MQ隊列,重試從新觸發該方法
5.commit database transaction
6.commit message transaction
複製代碼
上面這種時候沒有問題安全
1.start message transaction
2.receive message
3.start database transaction
4.update database
5.commit database transaction
6.commit message transaction #提交MQ事務出錯,消息放回至MQ隊列,重試從新觸發該方法
複製代碼
可能存在問題:會重複數據庫操做,由於database transaction不是使用JTA事務管理,因此database已經commit成功;如何避免,須要忽略重發消息,好比惟一性校驗等手段。bash
application.properties中配置了兩個數據源服務器
# 默認的Datasource配置
# spring.datasource.url = jdbc:mysql://localhost:3307/user
# spring.datasource.username = root
# spring.datasource.password = 123456
# spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.ds_user.url = jdbc:mysql://localhost:3307/js_user
spring.ds_user.username = root
spring.ds_user.password = 123456
spring.ds_user.driver-class-name = com.mysql.jdbc.Driver
spring.ds_order.url = jdbc:mysql://localhost:3307/js_order
spring.ds_order.username = root
spring.ds_order.password = 123456
spring.ds_order.driver-class-name = com.mysql.jdbc.Driver
複製代碼
自定義配置類文件app
@Configuration
public class DBConfiguration{
@Bean
@Primary
@ConfigurationProperties(prefix="spring.ds_user") #設置讀取在properties文件內容的前綴
public DataSourceProperties userDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource userDataSource(){
return userDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public JdbcTemplate userJdbcTemplate(@Qualifier("userDataSource") DataSource userDataSource){
return new JdbcTemplate(userDataSource);
}
@Bean
@ConfigurationProperties(prefix="spring.ds_order") #設置讀取在properties文件內容的前綴
public DataSourceProperties orderDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource orderDataSource(){
return userDataSourceProperties().initializeDataSourceBuilder().type(HikariDAtaSource.class).build();
}
@Bean
public JdbcTemplate orderJdbcTemplate(@Qualifier("orderDataSource") DataSource orderDataSource){
return new JdbcTemplate(orderDataSource);
}
}
複製代碼
Spring註解解釋(@Primary、@Qualifier)
實際調用類
public class CustomerService{
@Autowired
@Qualifier("userJdbcTemplate")
private jdbcTemplate userJdbcTemplate;
@Autowired
@Qualifier("orderJdbcTemplate")
private jdbcTemplate orderJdbcTemplate;
private static final String UPDATE_CUSTOMER_SQL;
private static final String INSERT_ORDER_SQL;
@Transactional #事務管理註解
public void createOrder(Order order){
userJdbcTemplate.update(UPDATE_CUSTOMER_SQL, order)
if(order.getTitle().contains("error1")){ #模擬異常出現
throw new RuntimeException("error1")
}
orderJdbcTemplate.update(INSERT_ORDER_SQL, order) #沒有使用事務,直接提交
if(order.getTitle().contains("error2")){ #模擬異常出現
throw new RuntimeException("error2")
}
}
}
複製代碼
關於上述過程的詳細說明:
由於使用了標籤 @Transactional的方式,使其在一個事務裏面執行
特別說明: @Transactional 若是沒有作任何配置的狀況下,則會使用DBConfiguration類中@Primart註解下的DataSource,用它去作datasource connection
spring DataSourceUtils源碼
spring DataSourceUtils 使用已有的connection,只是控制數據庫鏈接的釋放,不是事務。
鏈式事務管理器在 這個庫裏面
DBConfiguration類中添加一段@Bean
public PlatformTransactionManager transactionManager(){
DataSourceTransactionManager userTM = new DataSourceTransactionManager(userDataSource()) #看似方法調用,實則從spring容器中獲取
DataSourceTransactionManager orderTM = new DataSourceTransactionManager(orderDataSource())
# orderTM.setDataSource(orderDataSource()) 若是使用這種方式則不是從容器中去獲取了,由於orderTM不是spring容器管理
ChainedTransactionManager tm = new ChainedTransactionManager(userTM, orderTM) ## order先執行,user後執行
return tm;
}
複製代碼
連接事務管理器(Chaining transaction managers)
出現異常是否會有問題呢?
git代碼地址 ☚
git代碼地址 ☚
public OrderService{
Map disMap; # 用於存放已經處理的id
@Transactional
void ticketOrder(BuyTickerDTO dto){
String uid = createUUID(dto); # 建立並獲取數據的惟一id
if(!diMap.contains(uuid){ #disMap尚未處理過這個數據惟一id,則進入建立
Order order = createOrder(dto);
disMap.append(uid) ## 追加Map
}
}
userService.charge(dto); #調用user微服務
}
複製代碼
#經過調節限定,只有第一次支付的時候纔會扣餘額,被重複調用的時候就不會重複扣費用,經過paystatus判斷
UPDATE customer SET deposit = deposit - ${value}, paystatus = 'PAID' WHERE orderId = ${id} and paystatus = 'UNPAID'
複製代碼