1.LCN

2ByteTCC

3.Seata

Seata 是阿里开源的分布式事务框架,属于二阶段提交模式。

核心组件

  • 事务协调器 TC:维护全局和分支事务的状态,指示全局提交或者回滚。
  • 事务管理者 TM:开启、提交或者回滚一个全局事务。
  • 资源管理者 RM:管理执行分支事务的那些资源,向TC注册分支事务、上报分支事务状态、控制分支事务的提交或者回滚。

模式

AT(Auto Transaction)

AT模式是Seata默认的工作模式。需要基于支持本地 ACID 事务的关系型数据库,Java 应用,通过 JDBC 访问数据库。

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:提交异步化,非常快速地完成;回滚通过一阶段的回滚日志进行反向补偿。

步骤1开启全局事务;步骤2注册分支事务,这里对应着一阶段;步骤3提交或者回滚分支事务,对应着二阶段。

image.png

TCC

该模式工作分为三个阶段:prepare/commit/cancel。

  • TM向TC申请全局事务XID,传播给各个子调用。
  • 子调用的所在TM向TC注册分支事务,并执行本地prepare,并向TC报告执行结果。
  • TC根据各分支事务的执行结果确定二阶段是执行commit或rollback。

image.png

Saga 模式

Saga模式是Seata提供的长事务解决方案。例如全局事务中涉及到外部系统,无法管理它的资源管理器,让它改造成TCC也不好实行,这时就可以采用此类方案。

在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

image.png

XA模式

借助Seata框架的XA模式,会使XA方案更简单易用。使用前提:需要分支数据库支持XA 事务,应用为 Java应用,且使用JDBC访问数据库。

  • 执行阶段:业务sql在XA分支中执行,由分支事务的RM管理器管理,然后执行XA prepare。
  • 完成阶段:TM根据各个分支执行结果通过TC通知各个分支执行XA commit或者XA rollback。

image.png

Seata 各模式对比

image.png

工作流程

image.png

1.TM 请求 TC,开始一个新的全局事务,TC 会为这个全局事务生成一个 XID。

2.XID 在微服务调用链路的上下文中传播。

3.RM 开始执行分支事务,RM首先解析这条SQL语句,生成对应的UNDO_LOG,UNDO_LOG表中记录了分支ID,全局事务ID,以及事务执行的redo和undo数据以供二阶段恢复。

  1. {
  2. "branchId": 641789253,//分支事务ID
  3. "undoItems": [{
  4. "afterImage": {
  5. "rows": [{
  6. "fields": [{
  7. "name": "id",
  8. "type": 4,
  9. "value": 1
  10. }, {
  11. "name": "name",
  12. "type": 12,
  13. "value": "GTS"
  14. }, {
  15. "name": "since",
  16. "type": 12,
  17. "value": "2014"
  18. }]
  19. }],
  20. "tableName": "product"
  21. },
  22. "beforeImage": {
  23. "rows": [{
  24. "fields": [{
  25. "name": "id",
  26. "type": 4,
  27. "value": 1
  28. }, {
  29. "name": "name",
  30. "type": 12,
  31. "value": "TXC"
  32. }, {
  33. "name": "since",
  34. "type": 12,
  35. "value": "2014"
  36. }]
  37. }],
  38. "tableName": "product"
  39. },
  40. "sqlType": "UPDATE"
  41. }],
  42. "xid": "xid:xxx"//全局ID
  43. }

4.RM在同一个本地事务中执行业务SQL和UNDO_LOG数据的插入。在提交这个本地事务前,RM会向TC申请关于这条记录的全局锁。如果申请不到,则说明有其他事务也在对这条记录进行操作,因此它会在一段时间内重试,重试失败则回滚本地事务,并向TC汇报本地事务执行失败。

5.RM在事务提交前,申请到了相关记录的全局锁,因此直接提交本地事务,并向TC汇报本地事务执行成功。此时全局锁并没有释放,全局锁的释放取决于二阶段是提交命令还是回滚命令。

6.TC根据所有的分支事务执行结果,向RM下发提交或回滚命令。

7.RM如果收到TC的提交命令,首先立即释放相关记录的全局锁,然后把提交请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。异步队列中的提交请求真正执行时,只是删除相应 UNDO LOG 记录而已。

8.RM如果收到TC的回滚命令,则会开启一个本地事务,通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。将 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。否则,根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句并执行,然后提交本地事务达到回滚的目的,最后释放相关记录的全局锁。

隔离级别

应用

1)nacos安装 https://github.com/alibaba/nacos/releases,以standalone模式启动

1)seata服务端安装 https://github.com/seata/seata/releases

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  4. <version>2.1.1.RELEASE</version>
  5. <scope>compile</scope>
  6. <exclusions>
  7. <exclusion>
  8. <groupId>io.seata</groupId>
  9. <artifactId>seata-all</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>
  13. <dependency>
  14. <groupId>io.seata</groupId>
  15. <artifactId>seata-all</artifactId>
  16. <version>1.4.1</version>
  17. </dependency>
# springboot 整合单体 TC server 配置。
server:
  port: 18080

seata:
  config:
    type: nacos
  application-id: springboot-seata
  registry:
    type: nacos
  # seata 事务组编号 用于TC集群名
  tx-service-group: springboot-seata-group

spring:
  application:
    name: seata-distributed-transaction
  datasource:
    dynamic:
      datasource:
        # 设置 账号数据源配置
        account-ds:
          driver-class-name: com.mysql.cj.jdbc.Driver
          password: 123456
          url: jdbc:mysql://127.0.0.1:3306/accountdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
          username: root
          # 设置 订单数据源配置
        order-ds:
          driver-class-name: com.mysql.cj.jdbc.Driver
          password: 123456
          url: jdbc:mysql://127.0.0.1:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
          username: root
          # 设置商品 数据源配置
        product-ds:
          driver-class-name: com.mysql.cj.jdbc.Driver
          password: 123456
          url: jdbc:mysql://127.0.0.1:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
          username: root
      # 设置默认数据源或者数据源组 默认值即为master
      primary: order-ds   # 默认指定一个数据源
      # 开启对 seata的支持
      seata: true
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@GlobalTransactional
    public void purchase(String userId, String commodityCode, int orderCount) {
        storageFeignClient.deduct(commodityCode, orderCount);

        orderFeignClient.create(userId, commodityCode, orderCount);
    }

重要机制

(1)全局事务的回滚是如何实现的呢?

Seata 有一个重要的机制:回滚日志

每个分支事务对应的数据库中都需要有一个回滚日志表 UNDO_LOG,在真正修改数据库记录之前,都会先记录修改前的记录值,以便之后回滚。

在收到回滚请求后,就会根据 UNDO_LOG 生成回滚操作的 SQL 语句来执行。

如果收到的是提交请求,就把 UNDO_LOG 中的相应记录删除掉。

(2)RM 是怎么自动和 TC 交互的?

是通过监控拦截JDBC实现的,例如监控到开启本地事务了,就会自动向 TC 注册、生成回滚日志、向 TC 汇报执行结果。

(3)二阶段回滚失败怎么办?

例如 TC 命令各个 RM 回滚的时候,有一个微服务挂掉了,那么所有正常的微服务也都不会执行回滚,当这个微服务重新正常运行后,TC 会重新执行全局回滚。

参考

阿里开源的分布式事务框架 Seata
阿里分布式事务框架Seata原理解析
Seata是什么?一文了解其实现原理