Seata

Seata 是分布式事务解决方案,用于保证跨多个微服务的业务操作要么全部成功,要么全部失败,确保数据一致性。

微服务拆分后,一个业务需调用多个服务(例如:下单 → 扣库存 → 扣余额),Seata 防止部分服务成功、部分失败导致的数据错乱。

在spring中我们需要保持事物的一致性,所以使用注解@EnableTransactionManagement@Transactional,这样当我们出现异常的时候就可以进行回滚,注意这只是本地服务。

在Seata里面有三个角色

  • TC(事务协调者):像个 "大管家",盯着全局和分支事务的状态,决定是让事务提交成功还是回滚重来。

  • TM(事务管理器):是 "指挥官",划定全局事务范围,负责开启、提交或者回滚全局事务 。

  • RM(资源管理器):当 "办事员",管理分支事务要用的资源,还会和 TC 交流,汇报分支事务状态,执行提交或回滚 。

要想使用seata需要一个seata的客户端:下载地址:Seata-Server版本历史 | Apache Seata

安装之后在bin目录下使用:seata-server.bat启动,启动之后就可以访问端口http://127.0.0.1:7091/,我们的idea要想使用需要先引入依赖

XML 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

我们的服务模块要想使用seata就需要进行文件的配置:

XML 复制代码
service {
  #transaction service group mapping
  vgroupMapping.default_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

配置文件只需要进行服务的指向即可,8091是TC协调者的地址,7091是seata的服务地址。

我们现在已经可以使用seata了,现在我们要使用seata可以使用@GlobalTransactional我们只需要在最大的远程调用入口处使用即可。

二阶提交协议流程

当使用@GlobalTransactional时,事务会先在 Seata 的 TC(事务协调者)处注册并开启全局事务,生成全局事务 ID。 全局事务开启后,各模块的分支业务执行时:RM 会先向 TC 注册分支事务并获取分支 ID;执行 SQL 前,通过 WHERE 条件查询数据的前镜像(执行前状态);执行 SQL 后,查询后镜像(执行后状态);将前后镜像写入 undo_log 日志,随后在 同一本地事务中提交业务数据和 undo_log**(确保原子性),同时申请全局锁防止并发修改;最后向 TC 汇报分支事务状态(成功 / 失败),第一阶段结束。 若所有分支事务成功,TC 触发第二阶段提交:RM 收到提交请求后,异步删除 undo_log;若有分支失败,TC 触发第二阶段回滚:RM 开启本地事务,通过全局 ID 和分支 ID 找到 undo_log,对比当前数据与后镜像(检测是否有脏写,有则需人工处理),无脏写则根据前镜像生成回滚 SQL 恢复数据,最后删除 undo_log。。**

需注意:

  1. Seata 分支事务无需强制标注 @Transactional,AT 模式下 RM 会自动管理本地事务;若业务需要本地事务增强,可添加,但不是必须。

  2. 全局锁时机:全局锁的申请是在执行 SQL 修改数据时(而非 "本地保存之前"),用于防止并发事务脏写。

当我们开启调试之后,进行断点测试,就可以在seata的控制器看到全局锁,锁的表。

我们通过undo_log日志可以很清楚的看到前镜像和后镜像

bash 复制代码
{
  "@class": "org.apache.seata.rm.datasource.undo.BranchUndoLog",
  "xid": "192.168.74.1:8091:9688624833384463",
  "branchId": 9688624833384465,
  "sqlUndoLogs": [
    "java.util.ArrayList",
    [
      {
        "@class": "org.apache.seata.rm.datasource.undo.SQLUndoLog",
        "sqlType": "UPDATE",
        "tableName": "account_tbl",
        "beforeImage": {
          "@class": "org.apache.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "account_tbl",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "org.apache.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": 4,
                      "value": 1
                    },
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "money",
                      "keyType": "NULL",
                      "type": 4,
                      "value": 9946
                    }
                  ]
                ]
              }
            ]
          ]
        },
        "afterImage": {
          "@class": "org.apache.seata.rm.datasource.sql.struct.TableRecords",
          "tableName": "account_tbl",
          "rows": [
            "java.util.ArrayList",
            [
              {
                "@class": "org.apache.seata.rm.datasource.sql.struct.Row",
                "fields": [
                  "java.util.ArrayList",
                  [
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "id",
                      "keyType": "PRIMARY_KEY",
                      "type": 4,
                      "value": 1
                    },
                    {
                      "@class": "org.apache.seata.rm.datasource.sql.struct.Field",
                      "name": "money",
                      "keyType": "NULL",
                      "type": 4,
                      "value": 9928
                    }
                  ]
                ]
              }
            ]
          ]
        }
      }
    ]
  ]
}

Seata的四种模式

  1. AT模式 (Auto Transaction)

    • 总结: 自动生成反向SQL补偿,业务无感知。

    • 例子: 电商下单(自动回滚扣减的库存和优惠券)。

  2. TCC模式 (Try-Confirm-Cancel)

    • 总结: 需手动编写 Try(预留)、Confirm(提交)、Cancel(取消) 逻辑。

    • 例子: 转账业务(Try冻结A款、预留B款;Confirm实际划转;Cancel解冻/释放)。

  3. Saga模式

    • 总结: 长事务,服务编排,每个服务提交即生效,失败则逆向补偿。

    • 例子: 旅行预订(依次订机票、酒店,任一失败则取消之前成功的预订)。

  4. XA模式

    • 总结: 基于数据库XA协议,强一致,全程锁资源。

    • 例子: 银行核心系统跨行转账(严格保证A行扣款和B行入账同时成功或失败)。