在分布式系统中实现的事务就是分布式事务,分布式系统的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,只是控制数据库链接的释放,不是事务。
链式事务管理器在 这个库里面
@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'
复制代码