分布式事务

一、分布式事务概述

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的形式可能更加实用

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页