1. 事务管理
1.1 分布式事务
对于单独的SpringBoot项目,管理事务的方式一般都是在配置类中加上注解@EnableTransactionManagement开启事务管理器,再在对数据库操作,且需要添加事务的方法上加上注解@Transactional,使用动态代理为业务类的代码做增强。
但是对于分布式项目,微服务A调用微服务B的时候,当微服务B抛出异常,回滚事务,这时候微服务A是检测不到B服务异常的,因此A服务在B服务失败的情况下能依旧成功,不能保证全局事务的一致性。
1.2 Seata介绍
seata(Simple Extensible Autonomous Transaction Architecture),是阿里巴巴开源的一套的微服务架构分布式事务解决方案,对代码零侵入,集成到项目简单。
1.3 分布式事务生命周期
- TM向TC申请开启一个全局事务,TC生成一个公用的XID,并返回给TM
- TM将XID通过事务的执行链一次传递,保证了相同XID的方法处于一个事务中
- 得到XID的RM向TC注册成为本地事务分支,各个分支的业务正常执行,并且写入与当前sql相反的一条undo_log,用于异常后回滚
- RM在当前业务执行完之后,会向TC报告本地事务的情况 : 提交/回滚
- 当调用结束后,指针回到TM上,TM会告知TC分支事务已全部处理完毕
- TC根据通XID下的本地分支报告的状态,向各个分支RM发送指令
- 当所有RM都上报提交时,指令为删除undo_log
- 当至少有一个RM上报回滚时,所有RM执行undo_log操作,完成后删除
2. seata集成步骤
2.1 下载seata
下载地址: https://github.com/seata/seata/releases
2.2 修改配置
- registry.conf文件
- 注册方式,可以选择多种,选择file进入file的进行配置,选择nacos进入nacos进行配置
- 配置,使用file进行本机配置,也可以使用nacos配置中心,参考其他资料
- file.conf文件
- 可以使用file模式,也可以使用database模式,file模式可以直接使用,db模式需要配置数据库
- 使用file配置不需要更改设置
- 使用db配置需要设置数据库地址,以及添加数据库表,将db_store.sql导入创建的数据库即可
- 启动bin/seata-service.bat , 是seata正常启动
2.3 改造SpringCloud项目
- 引入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>
- 修改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
- 拷贝 file.conf 和 registry.conf 文件到 resources 目录下,并修改 file.conf 文件的事务组名,与yml中保持一致
- 自定义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 , 则使用注释的部分
- 在启动类上排除datasource的自动装配
- 注释掉单服务的事务管理注解
- 在集成了seata的数据库中添加undo_log表,该表的 sql 在 seata/config 文件夹中有
- 在需要开启全局事务的方法上加上注解
@GlobalTransactional
,被调用的方法不需要
注意 : 在所有使用全局事务的地方都需要如此集成(seata安装除外)
2.4 测试seata的全局事务
可以分别在服务A和服务B准备三个接口
- 服务A success() 服务B success()
- 服务A success() 服务B error()
- 服务A error() 服务B success()
1 成功的添加了数据, 2 和3 都失败进行了回滚,测试成功!