分布式事务理论到实战看这一篇就够了

分布式事务

一、概念

1. 何为分布式事务

一个事务包含多个操作,多个操作操作了多个数据源,这样的事务称为分布式事务。

2. CAP定理

画图举例说明:

例:用户在北京服务器下了单。

一致性:要想保证一致性,那么如果用户查询订单请求路由到了上海,因为上海没有这笔订单数据,所以只能报错来拒绝服务,让用户到北京服务器去查询

可用性:要想用户两个服务器都能访问,就只能不保证一致性,访问上海时就返回null,订单不存在。

这就是为什么CAP不能同时满足,一般都是满足AP或者CP的原因

3. BASE理论

BASE理论简单说就是,虽然CAP里面一般都只能满足CP和AP,但是这样满足不了大部分公司的要求,所以我们就降低了强一致性或者可用性,两者中和一下。

4. XA协议

X/Open 提出的分布式事务处理规范,分布式事务处理的工业标准。是一套跨语言的标准。

第一阶段:

第二阶段:

5. JTA

Java Transaction API
是Java根据XA规范提供的事务处理标准



二、事务处理方案

1. XA/JTA规范的两阶段提交

使用atomikos框架解决

传统的强一致性的方案。整个过程中的数据都会被锁住。

2. 可靠消息最终一致性方案

3. TCC(Try-confirm-cancel)两阶段补偿型方案

每个操作都需要实现预留方法,提交方法,取消方法,开发量比较大。它不依赖资源管理器(RM),通过对业务逻辑的分解来实现。

框架:
Atomikos,tcc-transaction,ByteTcc,支付宝GTS

4. 最大努力通知方案

类似银行的支付回调,会多次回调直到成功。



三、 代码实例

1. 跨库事务解决示例(不适用跨服务事务)

1.引入pom包,使用的是atomikos框架

 <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>1.3.2</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </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>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

2.配置多个数据源

spring.jta.transaction-manager-id=txManager

spring.datasource.druid.system-db.name=system-db
spring.datasource.druid.system-db.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
spring.datasource.druid.system-db.username=root
spring.datasource.druid.system-db.password=0490218292
spring.datasource.druid.system-db.initialSize=5
spring.datasource.druid.system-db.minIdle=5
spring.datasource.druid.system-db.maxActive=20
spring.datasource.druid.system-db.maxWait=60000
spring.datasource.druid.system-db.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.system-db.minEvictableIdleTimeMillis=30000
spring.datasource.druid.system-db.validationQuery=SELECT 1
spring.datasource.druid.system-db.validationQueryTimeout=10000
spring.datasource.druid.system-db.testWhileIdle=true
spring.datasource.druid.system-db.testOnBorrow=false
spring.datasource.druid.system-db.testOnReturn=false
spring.datasource.druid.system-db.poolPreparedStatements=true
spring.datasource.druid.system-db.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.system-db.filters=stat,wall
spring.datasource.druid.system-db.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.system-db.useGlobalDataSourceStat=true

spring.datasource.druid.business-db.name=business-db
spring.datasource.druid.business-db.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
spring.datasource.druid.business-db.username=root
spring.datasource.druid.business-db.password=0490218292
spring.datasource.druid.business-db.initialSize=5
spring.datasource.druid.business-db.minIdle=5
spring.datasource.druid.business-db.maxActive=20
spring.datasource.druid.business-db.maxWait=60000
spring.datasource.druid.business-db.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.business-db.minEvictableIdleTimeMillis=30000
spring.datasource.druid.business-db.validationQuery=SELECT 1
spring.datasource.druid.business-db.validationQueryTimeout=10000
spring.datasource.druid.business-db.testWhileIdle=true
spring.datasource.druid.business-db.testOnBorrow=false
spring.datasource.druid.business-db.testOnReturn=false
spring.datasource.druid.business-db.poolPreparedStatements=true
spring.datasource.druid.business-db.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.business-db.filters=stat,wall
spring.datasource.druid.business-db.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.business-db.useGlobalDataSourceStat=true

3.java代码配置数据源

注意这里的PACKAGE路径,代表写在里面的mapper会使用这个数据源。


@Component
@ConfigurationProperties(prefix="spring.datasource.druid.business-db")
public class BusinessProperties {

	private String url;
	
	private String username;
	
	private String password;

	private Integer initialSize;
	
	private Integer minIdle;
	
	private Integer maxActive;
	
	private Integer maxWait;
	
	private Integer timeBetweenEvictionRunsMillis;
	
	private Integer minEvictableIdleTimeMillis;
	
	private String validationQuery;
	
	private Integer validationQueryTimeout;
	
	private boolean testWhileIdle;
	
	private boolean testOnBorrow;
	
	private boolean testOnReturn;
	
	private boolean poolPreparedStatements;
	
	private Integer maxPoolPreparedStatementPerConnectionSize;
	
	private String connectionProperties;
	
	private boolean useGlobalDataSourceStat;
	
	private String filters;

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Integer getInitialSize() {
		return initialSize;
	}

	public void setInitialSize(Integer initialSize) {
		this.initialSize = initialSize;
	}

	public Integer getMinIdle() {
		return minIdle;
	}

	public void setMinIdle(Integer minIdle) {
		this.minIdle = minIdle;
	}

	public Integer getMaxActive() {
		return maxActive;
	}

	public void setMaxActive(Integer maxActive) {
		this.maxActive = maxActive;
	}

	public Integer getMaxWait() {
		return maxWait;
	}

	public void setMaxWait(Integer maxWait) {
		this.maxWait = maxWait;
	}

	public Integer getTimeBetweenEvictionRunsMillis() {
		return timeBetweenEvictionRunsMillis;
	}

	public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
		this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
	}

	public Integer getMinEvictableIdleTimeMillis() {
		return minEvictableIdleTimeMillis;
	}

	public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
		this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
	}

	public String getValidationQuery() {
		return validationQuery;
	}

	public void setValidationQuery(String validationQuery) {
		this.validationQuery = validationQuery;
	}

	public Integer getValidationQueryTimeout() {
		return validationQueryTimeout;
	}

	public void setValidationQueryTimeout(Integer validationQueryTimeout) {
		this.validationQueryTimeout = validationQueryTimeout;
	}

	public boolean isTestWhileIdle() {
		return testWhileIdle;
	}

	public void setTestWhileIdle(boolean testWhileIdle) {
		this.testWhileIdle = testWhileIdle;
	}

	public boolean isTestOnBorrow() {
		return testOnBorrow;
	}

	public void setTestOnBorrow(boolean testOnBorrow) {
		this.testOnBorrow = testOnBorrow;
	}

	public boolean isTestOnReturn() {
		return testOnReturn;
	}

	public void setTestOnReturn(boolean testOnReturn) {
		this.testOnReturn = testOnReturn;
	}

	public boolean isPoolPreparedStatements() {
		return poolPreparedStatements;
	}

	public void setPoolPreparedStatements(boolean poolPreparedStatements) {
		this.poolPreparedStatements = poolPreparedStatements;
	}

	public Integer getMaxPoolPreparedStatementPerConnectionSize() {
		return maxPoolPreparedStatementPerConnectionSize;
	}

	public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
		this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
	}

	public String getConnectionProperties() {
		return connectionProperties;
	}

	public void setConnectionProperties(String connectionProperties) {
		this.connectionProperties = connectionProperties;
	}

	public boolean isUseGlobalDataSourceStat() {
		return useGlobalDataSourceStat;
	}

	public void setUseGlobalDataSourceStat(boolean useGlobalDataSourceStat) {
		this.useGlobalDataSourceStat = useGlobalDataSourceStat;
	}

	public String getFilters() {
		return filters;
	}

	public void setFilters(String filters) {
		this.filters = filters;
	}

}

@Configuration
@MapperScan(basePackages = BusinessDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "businessSqlSessionFactory")
public class BusinessDataSourceConfig {

	static final String PACKAGE = "com.mmc.transaction.mapper.business";

	@Autowired
	private BusinessProperties businessProperties;

	@Bean(name = "businessDataSource")
    public DataSource businessDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaProperties(PojoUtil.obj2Properties(businessProperties));
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("businessProperties");
        ds.setPoolSize(5);
		ds.setTestQuery("SELECT 1");
        return ds;
    }

	@Bean
	public SqlSessionFactory businessSqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(businessDataSource());
		return sqlSessionFactoryBean.getObject();
	}

}

4.mapper文件

UserInfoMapper

public interface UserInfoMapper {

    void insertUserInfo(UserInfo userInfo);
}

UserInfoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mmc.transaction.mapper.business.UserInfoMapper">

    <resultMap id="BaseResultMap" type="com.mmc.transaction.bean.UserInfo">

        <id column="id" jdbcType="INTEGER" property="id" />
        <id column="userid" jdbcType="INTEGER" property="userid" />
        <result column="email" jdbcType="VARCHAR" property="email" />
        <result column="address" jdbcType="VARCHAR" property="address" />
    </resultMap>

    <insert id="insertUserInfo" parameterType="com.mmc.transaction.bean.UserInfo"  >
        insert into user_info(userid,email,address) values (#{userid},#{email},#{address})
    </insert>



</mapper>

5.测试代码

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;


    @Transactional
    public void insertUser(){
        User u = new User();
        u.setName("mmc");
        u.setAge(29);
        userMapper.insertUser(u);

        UserInfo userInfo = new UserInfo();
        userInfo.setUserid(11);
        userInfo.setEmail("990974807@qq.com");
        userInfo.setAddress("四川省");
        userInfoMapper.insertUserInfo(userInfo);
//        int i=30/0;
    }
}

当打开int i=30/0的注解时,两个数据库的插入操作都失效。事务处理成功。

2. TCC代码示例

git地址:https://github.com/liuyangming/ByteTCC-sample

posted @ 2020-03-17 15:22  女友在高考  阅读(626)  评论(0编辑  收藏  举报