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


    分布式事务

    一、概念

    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

  • 相关阅读:
    Linux 命令笔记
    MySQL指令笔记
    悲观锁与乐观锁
    缓存在高并发场景下的常见问题
    死锁相关问题
    Java并发性和多线程
    Java同步和异步,阻塞和非阻塞
    内存溢出和内存泄漏
    JavaAndroid项目配置文件笔记
    Maven安装配置
  • 原文地址:https://www.cnblogs.com/javammc/p/12510929.html
Copyright © 2020-2023  润新知