【若依】9、分布式事务解决方案(Seata)
❓❓❓@Transactional
只能处理本地事务,@DataSource
多数据源下事务该如何处理?
MySQL 事务实现原理
Seata
官网
简介
TC:事务协调者(Transaction Coordinator),这个负责维护全局事务和分支事务的状态,驱动全局事务提交或者回滚。
TM:事务管理器(Transaction Manager),这个是定义全局事务的范围,全局事务什么时候开启?什么时候回滚?什么时候提交等。
RM:资源管理器(Resource Manager),管理分支事务的资源,向 TC 注册分支事务的状态,驱动分支事务提交或者回滚。
自动补偿/反向补偿
🌰:微服务 A、B、C,现在在 A 中分别去调用 B 和 C,为了确保 B 和 C 的调用同时成功或者同时失败,那么就要使用分布式事务。假设先调用 B 再调用 C,B 调用完成后,事务就提交了,然后调用 C ,C 出错了,现在要回滚。此时 B 需要回滚,但是 B 的回滚并不是我们传统意义上所说的回滚,而是通过一条更新 SQL,将 B 中的数据复原,这个过程就叫做反向补偿。
安装 seata-server
seata 所提供的 seata-server 本质上就是一个 SpringBoot。
- 下载 seata-server:
- 解压,建议解压路径不要有中文,不要有空格
- 启动
seata-server
- Win:运行
./bin/seata-server.bat
- Linux/Mac:运行
./bin/seata-server.sh
- Win:运行
环境:Win、JDK1.8 直接运行没有报错
由于环境的原因,可能会遇到报错,请参考下面操作
修改一下./bin/seata-server.sh
脚本(以下内容只保留一行)
1
2
3
4
5
6
# JAVA_MAJOR_VERSION=$($JAVACMD -version 2>&1 | sed '1!d' | sed -e 's/"//g' | awk '{print $3}' | awk -F '.' '{print $2}')
# if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${BASEDIR}/logs/seata_gc.log:time,tags:filecount=10,filesize=102400"
# else
# JAVA_OPT="${JAVA_OPT} -Xloggc:${BASEDIR}/logs/seata_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
# fi
- 启动之后,浏览器输入http://localhost:7091,进入 seata 管理页面。
官网用例
Seata 快速开始 架构图:
RPC(Remote Procedure Call,远程过程调用):OpenFeign; 服务注册与发现:Eureka 使用的默认端口 8761; 把微服务注册到 TC 需要两个配置文件: seata-samples/file.conf at master · seata/seata-samples seata-samples/registry.conf at master · seata/seata-samples 🧡推荐下载方法:VSCode 在线编辑下载
GitHub + VSCode 在线编辑_月牙坠的博客-CSDN博客 关键:
@GlobalTransactional
TC:相当于 seata-server TM:相当于 @GlobalTransactional RM:相当于 business、account、order、storage
AT 模式
源码
TCC 模式
TCC:Try Confirm Cancel,也是基于两阶段提交发展出来的分布式事务处理方案。 Seata Tcc 模式 Feign 接口定义在公共模块,服务端实现,客户端继承。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@LocalTCC
public interface AccountServiceApi {
/*
prepare 是开发者手动调用的,commit 或 rollback 则是 seata 根据 prepare(所有的) 执行的情况,自动调用的。
*/
/**
* 一阶段操作
* 检查资源,如:检查账户是否存在,检查账户余额是否充足,余额充足的话,就冻结余额。
* @return
*/
@TwoPhaseBusinessAction(name = "accountServiceApi", commitMethod = "commit", rollbackMethod = "rollback")
@RequestMapping("/account/deduct/prepare")
boolean prepare(@RequestBody BusinessActionContext businessActionContext, @RequestParam("userId") @BusinessActionContextParameter(paramName = "userId") String userId, @RequestParam("money") @BusinessActionContextParameter(paramName = "money") Double money);
/**
* 二阶段提交
* 提交,主要是扣款操作(操作冻结金额即可)。
* @return
*/
@RequestMapping("/account/deduct/commit")
boolean commit(@RequestBody BusinessActionContext businessActionContext);
/**
* 二阶段回滚
* 回滚,将冻结的金额复原。
* @return
*/
@RequestMapping("/account/deduct/rollback")
boolean rollback(@RequestBody BusinessActionContext businessActionContext);
}
源码
XA 模式
XA 模式是 X/Open 组织定义的分布式事务处理标准。 XA 规范描述了全局的事务管理器与局部的资源管理器之间的接口,利用 XA 规范可以实现多个资源,例如数据库、MQ 等,在同一个事务中进行访问,这样就可以使得数据库的 ACID 属性在跨应用程序的时候依然有效。 目前所有主流的数据库基本上都支持 XA 协议,包括 MySQL。 MySQL 中的 XA 事务分为两种:
- 内部 XA:内部 XA 可以用作同一个 MySQL 实例下,跨多引擎的事务,这种一般使用 binlog 作为协调者。
- 外部 XA:外部 XA 可以参与到外部的分布式事务中,这种一般需要应用层介入作为协调者。
MySQL XA 事务
Seata XA 事务
- 首先 TM 开启全局的分布式事务。
- 不同的微服务开始执行,各个微服务(RM)依次执行
xa start
、SQL
、xa end
、xa prepare
,这是一阶段的操作。 - 在一阶段的操作中,
xa prepare
会将当前分支事务的状态报告给 TC。 - TC 判断一下,各个分支事务都执行成功了,此时就通知各个分支事务提交;如果分支事务中有人执行失败,那么此时就通知各个分支事务一起回滚。
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@Configuration public class DataSourceConfig { @Bean // 类型安全的属性注入 @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource druidDataSource() { return new DruidDataSource(); } @Bean // 多个 DataSource,优先用这个 @Primary public DataSource dataSource(DruidDataSource druidDataSource) { return new DataSourceProxyXA(druidDataSource); } // 配置 MyBatis @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
1
2
3
4
5
6
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.11</version>
</dependency>