文章

【若依】9、分布式事务解决方案(Seata)

❓❓❓@Transactional只能处理本地事务,@DataSource多数据源下事务该如何处理?

MySQL 事务实现原理

MySQL事务实现原理_月牙坠的博客-CSDN博客

Seata

官网

Seata

简介

Seata.png 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。

  1. 下载 seata-server:

seata-server

  1. 解压,建议解压路径不要有中文,不要有空格
  2. 启动seata-server
    1. Win:运行./bin/seata-server.bat
    2. Linux/Mac:运行./bin/seata-server.sh

环境: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
  1. 启动之后,浏览器输入http://localhost:7091,进入 seata 管理页面。

    官网用例

    Seata 快速开始 架构图: image.png 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 在线编辑下载 image.png GitHub + VSCode 在线编辑_月牙坠的博客-CSDN博客 关键:@GlobalTransactional TC:相当于 seata-server TM:相当于 @GlobalTransactional RM:相当于 business、account、order、storage

AT 模式

Seata AT 模式

源码

yueyazhui/seata_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);
}

源码

yueyazhui/seata_tcc

XA 模式

XA 模式是 X/Open 组织定义的分布式事务处理标准。 XA 规范描述了全局的事务管理器与局部的资源管理器之间的接口,利用 XA 规范可以实现多个资源,例如数据库、MQ 等,在同一个事务中进行访问,这样就可以使得数据库的 ACID 属性在跨应用程序的时候依然有效。 目前所有主流的数据库基本上都支持 XA 协议,包括 MySQL。 MySQL 中的 XA 事务分为两种:

  • 内部 XA:内部 XA 可以用作同一个 MySQL 实例下,跨多引擎的事务,这种一般使用 binlog 作为协调者。
  • 外部 XA:外部 XA 可以参与到外部的分布式事务中,这种一般需要应用层介入作为协调者。

    MySQL XA 事务

    MySQL XA 事务_月牙坠的博客-CSDN博客

    Seata XA 事务

    image.png

  1. 首先 TM 开启全局的分布式事务。
  2. 不同的微服务开始执行,各个微服务(RM)依次执行xa startSQLxa endxa prepare ,这是一阶段的操作。
  3. 在一阶段的操作中,xa prepare会将当前分支事务的状态报告给 TC。
  4. 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();
     }
    }
    

    🕳👀:JDBC 版本兼容问题,JDBC依赖需特定版本<version>8.0.11</version> 1.png

1
2
3
4
5
6
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
  <version>8.0.11</version>
</dependency>

源码

yueyazhui/seata_xa

本文由作者按照 CC BY 4.0 进行授权