一、分布式事务概述
1.CAP原理
2.ACID原理与BASE原理
3.基于XA协议两段提交(不建议使用)
4.事务补偿机制
5.基于本地消息表的最终一致方案
6.基于MQ消息队列的最终一致方案
二、分库分表所引发的问题
1.业务被拆分
2.多个独立数据库之间,事务不统一
3.数据可能出现不一致的情况
应用场景举例:
三、分布式事务解决方案
(一)、基于XA协议的两段提交的分布式事务解决方案
1.简介:
XA协议是有X/Open组织提出的分布式事务的规范
由一个事务管理器(TM)和多个资源管理器(RM)组成
提交分为两个阶段:prepare和commit,流程如下:
2.优缺点
优点:
保证了数据的强一致性
缺点:
commit阶段出现问题时,事务出现不一致,需要人工处理
效率低下,性能与本地事务相差10倍
3.兼容性
Mysql5.7以上均支持XA协议
Mysql Connector/J 5.0以上支持XA协议
Java系统中数据源采用Atomikos
4.项目示例
项目文件结构:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>xa-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xa-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
ConfigDB131.java
package com.xa.xademo.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.cj.jdbc.MysqlXADataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.UserTransaction;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* User: Administrator
* Date: 2021/1/14
* Time: 22:31
* Description: No Description
*/
@Configuration
@MapperScan(value = "com.xa.xademo.db131.dao", sqlSessionFactoryRef ="sqlSessionFactoryBean131")
public class ConfigDB131 {
@Bean("db131")
public DataSource db131() {
MysqlXADataSource xaDataSource = new MysqlXADataSource();
xaDataSource.setUser("xa");
xaDataSource.setPassword("xatest");
xaDataSource.setUrl("jdbc:mysql://192.168.56.131:3306/xa_131");
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(xaDataSource);
return atomikosDataSourceBean;
}
@Bean("sqlSessionFactoryBean131")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db131") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("mybatis/db131/*.xml"));
return sqlSessionFactoryBean;
}
@Bean("xaTransaction")
public JtaTransactionManager jtaTransactionManager() {
UserTransaction userTransaction = new UserTransactionImp();
UserTransactionManager userTransactionManager = new UserTransactionManager();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
ConfigDB132.java
package com.xa.xademo.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.cj.jdbc.MysqlXADataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* User: Administrator
* Date: 2021/1/14
* Time: 22:31
* Description: No Description
*/
@Configuration
@MapperScan(value = "com.xa.xademo.db131.dao", sqlSessionFactoryRef ="sqlSessionFactoryBean132")
public class ConfigDB132 {
@Bean("db132")
public DataSource db132() {
MysqlXADataSource xaDataSource = new MysqlXADataSource();
xaDataSource.setUser("xa");
xaDataSource.setPassword("xatest");
xaDataSource.setUrl("jdbc:mysql://192.168.56.132:3306/xa_132");
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(xaDataSource);
return atomikosDataSourceBean;
}
@Bean("sqlSessionFactoryBean132")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db132") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("mybatis/db132/*.xml"));
return sqlSessionFactoryBean;
}
}
模拟业务
XAService.java文件
package com.xa.xademo.service;
import com.xa.xademo.db131.dao.XA131Mapper;
import com.xa.xademo.db131.model.XA131;
import com.xa.xademo.db132.dao.XA132Mapper;
import com.xa.xademo.db132.model.XA132;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* Created with IntelliJ IDEA.
* User: Administrator
* Date: 2021/1/14
* Time: 23:08
* Description: No Description
*/
@Service
public class XAService {
@Resource
private XA131Mapper xa131Mapper;
@Resource
private XA132Mapper xa132Mapper;
@Transactional(transactionManager = "xaTransaction")
public void testXA(){
XA131 xa131 = new XA131();
xa131.setId(1);
xa131.setName("11111");
xa131Mapper.insert(xa131);
XA132 xa132 = new XA132();
xa132.setId(1);
xa132.setName("22222");
xa132Mapper.insert(xa132);
}
}
(二)、基于事务补偿机制的分布式事务解决方案
前言: 在提到分布式事务之前,需要特别说明一下,如果数据库由Mycat或者sharding-jdbc代理的,那么他们是默认支持分布式事务的,无需做额外的配置,就像使用普通的事务一样即可。
事务补偿机制原理:
针对每个操作,都要注册一个对应的补偿(撤销)操作;
失败时,调用补偿操作,回滚数据;
(三)、基于本地消息表的分布式事务解决方案
1.原理
采用BASE原理,保证事务最终一致
在一致性方面,允许一段时间内的不一致,但最终会一致
因此,实际业务中需要根据实时性等要求,判断该解决方案是否一致
2.实现
基于本地消息表的方案中,将本事务外操作,记录在消息表中
其他事务,提供操作接口
定时任务轮询本地消息表,将未执行的消息发送给操作接口
操作接口处理成功,返回成功标识,处理失败返回失败标识
定时任务接受到返回标识,更新消息状态
定时任务按照一定的周期,反复执行
对于屡次失败的消息可以设置最大失败次数
超过最大失败次数的消息,定时任务不再执行
等待人工处理
**3.架构图 **
4.优缺点
优点:事务拆分,避免了分布式事务,实现了最终一致性
缺点:需要确保重试时的幂等性
(四)、基于MQ消息队列的分布式事务解决方案
1.原理
流程,原理基本与”本地消息表“一致
2.与本地消息表的区别:
本地消息表改为了MQ消息队列;
定时任务替换为消费者。
3.总体流程:
4.优缺点
实时性高,可靠性高,不需要依赖于定时任务
对于公司内部的系统而言,MQ的形式可能更加实用