1. 事务管理

1.1 分布式事务

对于单独的SpringBoot项目,管理事务的方式一般都是在配置类中加上注解@EnableTransactionManagement开启事务管理器,再在对数据库操作,且需要添加事务的方法上加上注解@Transactional,使用动态代理为业务类的代码做增强。

但是对于分布式项目,微服务A调用微服务B的时候,当微服务B抛出异常,回滚事务,这时候微服务A是检测不到B服务异常的,因此A服务在B服务失败的情况下能依旧成功,不能保证全局事务的一致性。

1.2 Seata介绍

seata(Simple Extensible Autonomous Transaction Architecture),是阿里巴巴开源的一套的微服务架构分布式事务解决方案,对代码零侵入,集成到项目简单。

1.3 分布式事务生命周期
  1. TM向TC申请开启一个全局事务,TC生成一个公用的XID,并返回给TM
  2. TM将XID通过事务的执行链一次传递,保证了相同XID的方法处于一个事务中
  3. 得到XID的RM向TC注册成为本地事务分支,各个分支的业务正常执行,并且写入与当前sql相反的一条undo_log,用于异常后回滚
  4. RM在当前业务执行完之后,会向TC报告本地事务的情况 : 提交/回滚
  5. 当调用结束后,指针回到TM上,TM会告知TC分支事务已全部处理完毕
  6. TC根据通XID下的本地分支报告的状态,向各个分支RM发送指令
  7. 当所有RM都上报提交时,指令为删除undo_log
  8. 当至少有一个RM上报回滚时,所有RM执行undo_log操作,完成后删除

2. seata集成步骤

2.1 下载seata

下载地址: https://github.com/seata/seata/releases

2.2 修改配置
  1. registry.conf文件

springcloud集成oauth2完整示例 springcloud集成seata_微服务

  1. 注册方式,可以选择多种,选择file进入file的进行配置,选择nacos进入nacos进行配置

springcloud集成oauth2完整示例 springcloud集成seata_java_02

springcloud集成oauth2完整示例 springcloud集成seata_java_03

  1. 配置,使用file进行本机配置,也可以使用nacos配置中心,参考其他资料

springcloud集成oauth2完整示例 springcloud集成seata_全局事务_04

springcloud集成oauth2完整示例 springcloud集成seata_微服务_05

  1. file.conf文件
  1. 可以使用file模式,也可以使用database模式,file模式可以直接使用,db模式需要配置数据库
  1. 使用file配置不需要更改设置
  2. 使用db配置需要设置数据库地址,以及添加数据库表,将db_store.sql导入创建的数据库即可

springcloud集成oauth2完整示例 springcloud集成seata_spring_06

springcloud集成oauth2完整示例 springcloud集成seata_全局事务_07

  1. 启动bin/seata-service.bat , 是seata正常启动

2.3 改造SpringCloud项目

  1. 引入seata依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-seata</artifactId>
    <version>2.1.0.RELEASE</version>
    <exclusions>
        <exclusion>
            <artifactId>seata-all</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <!--该版本和下载的seata版本匹配-->
    <version>1.1.0</version>
</dependency>
  1. 修改yml配置
server:
  port: 8085

spring:
  application:
    name: seata-one

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    alibaba:
      seata:
        tx-service-group: fsp_tx_group #添加tx事务组名,需要和file.conf对应

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.1.7:3306/seata-one?serverTimezone=GMT
    username: root
    password: 
    type: com.alibaba.druid.pool.DruidDataSource

mybatis-plus:
  mapper-locations: classpath:cn/qiuming/mapper/*Mapper.xml

dubbo:
  scan:
    base-packages: cn.qiuming.dubbo.api 

  protocol:
    name: dubbo 
    port: 19991 

  cloud:
    subscribed-services: seata-two
  registry:
    address: spring-cloud://192.168.1.7
  1. 拷贝 file.conf 和 registry.conf 文件到 resources 目录下,并修改 file.conf 文件的事务组名,与yml中保持一致
  2. springcloud集成oauth2完整示例 springcloud集成seata_spring_08


  3. springcloud集成oauth2完整示例 springcloud集成seata_java_09

  4. 自定义DataSource的配置类,将DataSource交给seata进行管理
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.ArrayList;

@Configuration
public class DataSourceConfiguration {

    @Value("${mybatis-plus.mapper-locations}")
    //@Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        //SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSourceProxy);
        factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //事务管理工厂
        factory.setTransactionFactory(new SpringManagedTransactionFactory());
        return factory;
    }

    @Bean
    public DataSourceProxy dataSource() {
        return new DataSourceProxy(druidDataSource());
    }
}

注意: 如果使用的mybatis-plus如上配置 , 如果使用的是mybatis , 则使用注释的部分

  1. 在启动类上排除datasource的自动装配
  2. 注释掉单服务的事务管理注解
  3. 在集成了seata的数据库中添加undo_log表,该表的 sql 在 seata/config 文件夹中有
  4. 在需要开启全局事务的方法上加上注解@GlobalTransactional,被调用的方法不需要

注意 : 在所有使用全局事务的地方都需要如此集成(seata安装除外)

2.4 测试seata的全局事务

可以分别在服务A和服务B准备三个接口

  1. 服务A success() 服务B success()
  2. 服务A success() 服务B error()
  3. 服务A error() 服务B success()

1 成功的添加了数据, 2 和3 都失败进行了回滚,测试成功!