【Spring Cloud微服务】9.一站式掌握 Seata:架构设计与 AT、TCC、Saga、XA 模式选型指南

发布于:2025-09-03 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、Seata 框架概述

Seata(Simple Extensible Autonomous Transaction Architecture)是一款开源的分布式事务解决方案,其愿景是让分布式事务的使用像本地事务一样简单和高效。

它的核心思想是:通过一个全局事务来协调和管理多个分支事务(即本地事务)的提交和回滚,从而保证全局数据的一致性。在微服务架构中,业务逻辑通常需要跨多个服务、多个数据库,Seata 正是为了解决由此产生的分布式事务问题而生的。


二、核心功能特性

  1. 多事务模式支持:这是 Seata 最强大的特性,它提供了多种分布式事务解决方案,以适应不同的业务场景。

    • AT 模式(默认, Automatic Transaction):
      • 无侵入:对业务代码几乎无侵入,只需要使用 @GlobalTransactional 注解即可。
      • 原理:基于两阶段提交(2PC) 的增强版。它通过代理数据源,在第一阶段就提交本地事务,并通过全局锁来保证隔离性。第二阶段由 TC 统一指挥进行提交或回滚(通过 undo_log 日志进行补偿回滚)。
      • 优点:使用简单,性能较好。
      • 缺点:对数据库支持有要求(需要支持本地事务和行锁),全局锁的存在会对性能有轻微影响。
    • TCC 模式(Try-Confirm-Cancel):
      • 侵入性:需要业务代码实现 Try、Confirm、Cancel 三个接口。
      • 原理:也是两阶段提交,但需要开发者自己实现资源检查和预留(Try)、确认提交(Confirm)、补偿回滚(Cancel)的逻辑。
      • 优点:性能非常高,不存在全局锁,能获得更好的并发性。适用于对性能要求高、有更高隔离性要求的场景(如金融、电商等)。
      • 缺点:代码侵入性强,业务改造量大,设计模式需要谨慎。
    • Saga 模式:
      • 侵入性:基于状态机或注解+服务的形式,需要定义每个服务的补偿服务。
      • 原理:一种长事务解决方案。每个参与者都提交本地事务,如果有一个参与者失败,则通过之前成功的参与者提供的补偿操作来逆向回滚整个事务。
      • 优点:适用于业务流程长、需要调用大量服务的场景(如机票预订、旅游套餐),参与者可以异步执行,吞吐量高。
      • 缺点:不保证隔离性,可能出现“脏写”,需要业务逻辑能够处理这种中间状态(例如通过预留、校验等机制)。
    • XA 模式:
      • 标准:基于 X/Open 组织定义的 XA 协议的实现。
      • 原理:也是两阶段提交。TM 和 RM 之间通过 XA 接口进行通信。在第一阶段,所有分支事务执行但不提交(XA prepare);第二阶段,TC 通知所有分支提交或回滚。
      • 优点:强一致性,事务隔离性好。得到了多数主流数据库的支持。
      • 缺点:资源锁定时间长,在第二阶段完成前,所有资源都被锁定,性能较差。
  2. 高可用(High Availability):TC(事务协调器)支持基于注册中心(如 Nacos、Eureka)和配置中心(如 Apollo、Nacos)的集群部署,避免单点故障。

  3. 开箱即用:提供了丰富的配置选项,与主流的微服务框架(Spring Cloud、Dubbo)、注册中心、配置中心都有很好的集成。

  4. 丰富的生态:与多家云厂商和开源项目做了适配和集成。


三、整体架构与三大角色

Seata 的架构中包含了三个核心角色,其交互关系如下图所示:

服务端 Server - 独立部署
客户端 Client - 集成于应用服务内
1. 开始全局事务
2. 注册分支事务
3. 通知全局提交/回滚
4. 异步通知分支提交/回滚
TC: 事务协调器
维护全局事务状态
TM: 事务管理器
定义全局事务边界
RM: 资源管理器
管理分支事务资源

1. Transaction Coordinator (TC) - 事务协调器(Seata Server)

  • 职责:事务的大脑和指挥官。它是独立的中间件服务器,需要单独部署。
  • 功能:
    • 维护全局事务和分支事务的状态。
    • 驱动全局事务的提交或回滚。
    • 管理全局锁(特别是在 AT 模式下)。
  • 部署:通常以集群模式部署,通过注册中心提供服务发现。

2. Transaction Manager ™ - 事务管理器(集成在客户端)

  • 职责:全局事务的发起者。它定义了全局事务的边界。
  • 功能:
    • 向 TC 发起指令,开启一个全新的全局事务。
    • 向 TC 发起指令,提交或回滚一个全局事务。
  • 位置:通常位于发起全局事务的微服务应用程序中。例如,在订单服务中,一个创建订单的方法可能会被 @GlobalTransactional 注解标记,该方法中的代码就扮演了 TM 的角色。

3. Resource Manager (RM) - 资源管理器(集成在客户端)

  • 职责:分支事务的执行者。负责管理本地资源(通常是数据库)。
  • 功能:
    • 向 TC 注册分支事务,并报告分支事务的状态。
    • 接收来自 TC 的指令,驱动分支事务的本地提交或回滚。
    • 在 AT 模式下,RM 会代理数据源,自动生成和执行 SQL 的逆向回滚日志(undo_log)。
  • 位置:每一个参与分布式事务的微服务应用程序中都有一个 RM。它负责操作自己管辖的数据库。

四、工作流程(以最常用的 AT 模式为例)

假设一个下单流程:订单服务 -> 库存服务 -> 账户服务

  1. TM 开启全局事务:
    • 用户调用订单服务的“创建订单”接口。
    • 订单服务中的 TM(被 @GlobalTransactional 注解的方法)向 TC 申请开启一个全局事务(XID),TC 会生成一个唯一的 XID 并返回。
  2. RM 注册分支事务:
    • 订单服务执行本地事务,在向订单表插入数据前,RM 会先拦截 SQL,生成查询快照和回滚日志(undo_log),并写入数据库。
    • 订单服务的 RM 向 TC 注册一个分支事务,并将这个分支事务与之前的 XID 关联。
    • 订单服务提交本地事务。
  3. 调用链传播 XID:
    • 订单服务通过 Feign/Ribbon 调用库存服务。Seata 会自动将 XID 通过请求头(例如 tx-seata-xid)传递到下游服务。
  4. 下游服务执行:
    • 库存服务收到请求,其 RM 也感知到了这个 XID。
    • 库存服务执行扣减库存的本地事务,同样生成 undo_log 并向 TC 注册属于自己的分支事务,然后提交本地事务。
    • 账户服务同理。
  5. 全局提交或回滚:
    • 成功情况:所有分支事务都成功执行并注册。TM(订单服务)会向 TC 发起全局提交的指令。TC 会异步地通知所有 RM 删除各自的 undo_log 日志。整个过程非常快,因为一阶段已经提交了。
    • 失败情况:如果账户服务扣款失败,它会抛出异常。这个异常会沿着调用链向上传播,最终被订单服务中的 TM 捕获。
      • TM 会向 TC 发起全局回滚的指令。
      • TC 根据 XID 找到所有相关分支事务,并向它们的 RM 下达回滚命令。
      • 各个 RM 收到回滚命令后,根据本地数据库中的 undo_log 日志,生成反向的 SQL 语句(例如 INSERT 变成 DELETE,UPDATE 变回旧值)并执行,完成数据回滚,最后删除 undo_log

总结

特性/模式 AT 模式 TCC 模式 Saga 模式 XA 模式
侵入性 低(无侵入) 高(需实现接口) 中(需定义补偿) 低(无侵入)
性能 较好 非常好 非常好(可异步) 差(资源锁定)
隔离性 读未提交(靠全局锁) 依赖业务实现 无隔离 强隔离
适用场景 大部分场景,希望简单高效 高性能要求,金融支付 长事务,业务流程多的系统 传统银行、强一致性需求

Seata 通过其清晰的角色划分和对多种模式的支持,为复杂的分布式系统环境提供了灵活而强大的一致性保证,是构建现代微服务架构不可或缺的重要组件。

五、一个大比喻:Seata 就像一次「跨银行转账」

想象一下,你要从【建设银行】的账户转 100 块钱到你的朋友在【工商银行】的账户。这个操作其实就是一个典型的分布式事务,它涉及了两个不同的银行系统(两个独立的数据库)。


Seata 的三大角色在这场“转账”中是谁?

  1. TC (事务协调器) - 总行清算中心

    • 它是谁:一个独立的核心机构,不属于任何一家地方银行。它知道全国所有跨行交易的进展。
    • 干什么活:
      • 记录每一笔跨行转账的全局状态(开始了?成功了?失败了?)。
      • 负责最终下达命令:“所有银行,请最终确认提交!” 或者 “所有银行,立刻撤销刚才的操作!”
    • 特点:必须非常可靠,所以通常是集群部署(好几个备份清算中心),防止一个挂了导致全国转账瘫痪。
  2. TM (事务管理器) - 建设银行的那个柜员/ATM机

    • 它是谁:发起整个事务的人。就是你面对的那个帮你操作转账的柜员,或者那台ATM机。
    • 干什么活:
      • 他/它先向“总行清算中心(TC)”汇报:“您好,我要开始一笔从建行转工行的转账了,编号是XXX”。
      • 如果整个流程顺利,他/它就向TC发起“最终提交”的指令。
      • 如果中途出错(比如工行账户不存在),他/它就立刻向TC发起“回滚”的指令。
  3. RM (资源管理器) - 建设银行和工商银行自己的账本系统

    • 它是谁:每个参与事务的资源个体。在这里就是【建设银行】的数据库和【工商银行】的数据库。
    • 干什么活:
      • 建行RM:负责从你的账户上临时扣下100块(但先不真正给别人)。
      • 工行RM:负责在你朋友的账户上临时加上100块(但先不让他用)。
      • 它们都要向“总行清算中心(TC)”汇报:“我这边临时操作完了,状态OK,随时等待您的最终命令”。
      • 最后听从TC的命令,要么把“临时”变成“正式”,要么撤销刚才的临时操作。

六、Seata 的四种模式就是四种不同的转账策略

现在,总行清算中心(TC)提供了几种不同的转账流程供你选择:

1. AT模式(自动挡模式) - 「预授权」转账

这是最常用、最省心的模式,就像你用信用卡的预授权。

  • 流程:

    1. 第一阶段:
      • 建行RM:先从你账户冻结100块(就像酒店冻结你信用卡一定额度,钱还没划走,但你也不能用了)。
      • 工行RM:在你朋友账户里先标记有100块即将到账(但还不能用)。
      • 两边都向TC报告:“预授权成功”。
    2. 第二阶段:
      • 如果成功:TC说“确认”,两家银行就把预授权变成实际扣款和实际入账。
      • 如果失败:TC说“撤销”,建行就解冻你的100块,工行就取消那100块的标记。
  • 优点:你(程序员)几乎无感,Seata帮你自动搞定了一切,就像开车用自动挡。

  • 缺点:用了“全局锁”(即冻结资金),性能有一点点损失。

2. TCC模式(手动挡模式) - 「自己打电话确认」转账

这个模式要求高,需要你(业务开发者)自己介入更多,就像大额转账需要你亲自打电话一步步确认。

  • 流程:

    1. Try (尝试)阶段:你自己打电话。
      • 打给建行:“麻烦你帮我试试看能不能扣100块?”(资源检查和预留)。
      • 打给工行:“麻烦你试试看能不能收100块?”。
    2. Confirm (确认)阶段:如果两边都回电话说“没问题”,你就再打一次电话:“好,请正式操作!”
    3. Cancel (取消)阶段:如果工行说“账户不对,收不了”,你就得再打给建行:“刚才的尝试作废,解封吧。”
  • 优点:性能最好,控制力最强,没有全局锁。

  • 缺点:太麻烦!你需要写(打)很多代码(电话)来实现Try、Confirm、Cancel三个操作。

3. Saga模式 - 「先垫付,后报销」模式

适用于一个非常长的链条,比如公司组织团建,你先自己垫钱,最后统一报销。

  • 流程:

    1. 你先自己掏钱买了机票(提交本地事务)。
    2. 你又自己掏钱订了酒店(提交本地事务)。
    3. 最后你申请公司报销。
      • 如果报销成功:完美!
      • 如果报销失败:公司不认这笔钱。那么你就得反向操作:把机票退掉、把酒店退掉(补偿操作)。
  • 优点:吞吐量高,适合长流程业务。

  • 缺点:可能你退机票的时候发现已经不能退了(缺乏隔离性),就得自己承担损失。

4. XA模式 - 「柜台排队,同时盖章」模式

传统银行的老做法,非常严格但效率低。

  • 流程:

    1. 建行和工行的柜员都拿到了你的转账单。
    2. 他们互相打电话确认:“我准备好了,你呢?”“我也准备好了。”
    3. 等到两边都确认准备好后,在同一时刻一起盖章生效。
  • 优点:强一致性,最安全。

  • 缺点:在准备和确认的过程中,你账户的钱和对方账户的钱都被锁住,不能做任何其他操作,效率最低。

总结一下

  • 想省心,就用 AT模式(默认),像开自动挡汽车。
  • 追求极致性能和高控制,不怕麻烦就用 TCC模式,像开手动挡赛车。
  • 业务流程特别长,就用 Saga模式,像先垫付后报销。
  • 传统、强一致的系统(如银行核心系统),会用 XA模式,像老式的柜台同步操作。

所以,Seata 本质上就是一个分布式事务的调度中心,它提供了好几种“工作流程”来保证:要么大家都成功,要么都失败,绝不会出现“我的钱扣了,对方却没收到”的尴尬局面。

好的,我们接着用比喻和实例的方式,来深入讲解如何实现 Seata 的这几种模式。


七、如何实现 AT 模式(自动挡模式)

AT 模式是 Seata 的默认模式,对代码侵入性极低,就像开自动挡汽车。

实现步骤(基于 Spring Cloud + Spring Boot):

  1. “考驾照” - 引入依赖与配置
    首先,你需要在你的微服务项目中“考取”操作 Seata 的资格。

    <!-- 在您的 order-service, storage-service, account-service 的 pom.xml 中 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    
  2. “给车加油和设置导航” - 配置 Seata
    在每个微服务的 application.yml 中,告诉它们 TC(事务协调器,即 Seata-Server)在哪里。

    seata:
      application-id: order-service # 当前服务名
      tx-service-group: my_tx_group # 事务组名,与seata-server配置对应
      registry:
        type: nacos # 注册中心类型(假设你用nacos)
        nacos:
          server-addr: localhost:8848
          namespace: public
      config:
        type: nacos # 配置中心类型
        nacos:
          server-addr: localhost:8848
          namespace: public
    
  3. “创建交通规则” - 建立 undo_log 表
    AT 模式的核心是回滚日志。你需要在每一个业务涉及的数据库中,都创建一张 undo_log 表。这张表就像是汽车的“黑匣子”,记录了数据修改前的样子,以便出问题时能还原。
    (建表 SQL 在 Seata 官方文档的脚本里可以找到)

  4. “握紧方向盘,踩下油门” - 使用全局事务注解
    现在,你只需要在发起全局事务的入口方法上,加一个神奇的注解 @GlobalTransactional

    @RestController
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        @PostMapping("/createOrder")
        @GlobalTransactional(name = "createOrder", rollbackFor = Exception.class) // 就是这个注解!
        public String createOrder(Order order) {
            // 1. 扣减库存(调用库存服务)
            storageService.decrease(order.getProductId(), order.getCount());
            // 2. 扣减余额(调用账户服务)
            accountService.decrease(order.getUserId(), order.getMoney());
            // 3. 创建订单(本地事务)
            orderService.create(order);
            return "订单创建成功!";
        }
    }
    

发生了什么?
当程序运行:

  • Seata 会自动代理你的数据源。
  • 在调用 storageService.decrease 时,Seata 会拦截 SQL,先将库存修改前的数据作为“回滚日志”存入 undo_log 表,然后再执行扣减。
  • 扣减成功后,它会向 TC 注册一个分支事务,报告:“我是库存分支,我预执行成功了”。
  • 账户服务和订单服务执行同样的操作。
  • 如果所有分支都成功,TM(@GlobalTransactional 注解的方法)会通知 TC,TC 再命令所有 RM 删除各自的 undo_log 日志,事务真正提交。
  • 如果中间任何一步失败(比如余额不足),TM 会通知 TC,TC 命令所有 RM:“请根据你们的 undo_log 黑匣子,把数据恢复原状!”。

总结:实现 AT 模式 ≈ 引入依赖 + 配置 TC 地址 + 建 undo_log 表 + 加一个注解。


八、使用 Seata 实现 TCC 模式(手动挡模式)

TCC 模式需要你亲自编写业务逻辑,实现“尝试”、“确认”、“取消”三个操作,就像开手动挡车。

实现步骤:

  1. “定义三个踏板” - 编写 TCC 接口
    你需要为你的业务资源定义一个接口,包含 try, confirm, cancel 三个方法。

    @LocalTCC
    public interface AccountTccAction {
        // try阶段:资源检查和预留
        // @BusinessActionContextParameter 用于将参数传递到confirm/cancel阶段
        @TwoPhaseBusinessAction(name = "accountTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
        boolean tryDecrease(@BusinessActionContextParameter(paramName = "userId") String userId,
                            @BusinessActionContextParameter(paramName = "money") BigDecimal money);
        // confirm阶段:真正执行
        boolean confirm(BusinessActionContext context);
        // cancel阶段:补偿恢复
        boolean cancel(BusinessActionContext context);
    }
    
  2. “制造三个踏板” - 实现 TCC 接口
    在实现类中,你手动编写扣款业务的三个阶段逻辑。

    @Service
    public class AccountTccActionImpl implements AccountTccAction {
    
        @Autowired
        private AccountMapper accountMapper;
    
        @Override
        public boolean tryDecrease(String userId, BigDecimal money) {
            // 1. 检查余额是否足够
            Account account = accountMapper.selectById(userId);
            if (account.getResidue().compareTo(money) < 0) {
                throw new RuntimeException("账户余额不足");
            }
            // 2. 尝试阶段:冻结金额(预留资源)
            accountMapper.updateFrozen(userId, money); // SQL: UPDATE account SET frozen = frozen + #{money} WHERE user_id = #{userId}
            return true;
        }
    
        @Override
        public boolean confirm(BusinessActionContext context) {
            // 获取try阶段的参数
            String userId = (String) context.getActionContext("userId");
            BigDecimal money = (BigDecimal) context.getActionContext("money");
            // 确认阶段:扣除冻结的金额
            accountMapper.updateFrozenToUsed(userId, money); // SQL: UPDATE account SET residue = residue - #{money}, frozen = frozen - #{money} WHERE user_id = #{userId}
            return true;
        }
    
        @Override
        public boolean cancel(BusinessActionContext context) {
            // 获取try阶段的参数
            String userId = (String) context.getActionContext("userId");
            BigDecimal money = (BigDecimal) context.getActionContext("money");
            // 补偿阶段:释放冻结的金额
            accountMapper.updateFrozenToResidue(userId, money); // SQL: UPDATE account SET frozen = frozen - #{money} WHERE user_id = #{userId}
            return true;
        }
    }
    
  3. “挂挡给油” - 在业务中调用 TCC 服务
    在你的业务代码中,不再直接调用普通的账户服务,而是调用这个 TCC 服务。

    @RestController
    public class OrderController {
    
        // 注入TCC服务,而不是普通的AccountService
        @Autowired
        private AccountTccAction accountTccAction;
    
        @PostMapping("/createOrder")
        @GlobalTransactional(name = "createOrderTcc", rollbackFor = Exception.class)
        public String createOrder(Order order) {
            // ...
            // 调用TCC服务的try阶段
            accountTccAction.tryDecrease(order.getUserId(), order.getMoney());
            // ...
        }
    }
    

核心思想:TCC 模式就是把一个大事务拆成两个阶段。

  • 一阶段 (Try):不做最终操作,只做准备工作(检查、预留资源、冻结资金)。
  • 二阶段:
    • 如果所有 Try 都成功,就执行 Confirm(确认,使用预留的资源)。
    • 如果有一个 Try 失败,就执行 Cancel(取消,释放预留的资源)。

注意事项:

  • 空回滚:Try 阶段可能因为网络问题没执行,但 Cancel 被执行了。你的 Cancel 方法需要能处理这种情况(查一下有没有冻结记录,没有就不操作)。
  • 幂等性:因为网络问题,Confirm 或 Cancel 可能会被重复调用,你的方法需要保证多次调用结果一样(比如用事务状态表来判断)。

九、使用 Seata 实现 Saga 和 XA 模式

1. Saga 模式(长事务模式)

Saga 模式通常用于业务流程非常长的场景,比如一个旅游预订流程,包含订机票、订酒店、办签证等多个步骤,每个步骤都可能耗时很久。

实现方式(状态机模式):

  1. 定义状态机JSON:你需要用一个 JSON 文件来详细定义整个业务流程的每一个步骤(State),以及每个步骤失败后对应的补偿操作(Compensation)。
  2. 部署状态机:将这个 JSON 文件部署到 Seata 的 Saga 状态机引擎中。
  3. 触发执行:你的程序只需要发起一个请求,状态机引擎就会自动按照你定义的流程一步步执行,并在任何一步失败时,自动反向执行之前所有成功步骤的补偿操作。

特点:

  • 一阶段就提交:每个服务都直接提交本地事务,所以没有锁,性能高。
  • 全靠补偿:一致性靠的是补偿操作,如果补偿操作也失败了,就需要人工介入处理。不保证隔离性。

适用场景:电商订单履约、物流系统、工作流审批等长时间运行的业务流程。

2. XA 模式(传统强一致模式)

XA 模式是数据库层面提供的标准协议,Seata 也提供了支持。

实现步骤:

  1. 切换模式:在你的 application.yml 中,将数据源代理模式改为 XA。

    seata:
      data-source-proxy-mode: XA # 默认是 AT
    
  2. 使用注解:和 AT 模式一样,在事务入口使用 @GlobalTransactional 注解。

背后原理:

  • 一阶段:Seata 会调用 XA StartXA EndXA Prepare。此时,数据库会执行 SQL,但并不提交,而是将事务置于“准备就绪”状态,数据对其他事务不可见。
  • 二阶段:
    • 提交:TC 通知所有 RM XA Commit,所有数据库同时提交。
    • 回滚:TC 通知所有 RM XA Rollback,所有数据库同时回滚。

特点:

  • 强一致性:在整个事务结束前,数据都被锁住,完美保证隔离性。
  • 性能差:因为资源锁定时间长,并发性能是几种模式中最差的。

适用场景:传统金融、银行等对强一致性要求极高,且并发量不是天文数字的内部系统。

总结与选择

模式 实现关键 比喻 适用场景
AT 加注解 @GlobalTransactional 自动挡 绝大部分场景,追求简单和效率
TCC 自己写 tryconfirmcancel 三个方法 手动挡 高性能、高并发场景,如资金交易
Saga 用状态机JSON定义流程和补偿 先做后报 长流程业务,如订单履约、工作流
XA 配置 data-source-proxy-mode: XA + 加注解 @GlobalTransactional 同步盖章 传统行业,强一致性要求极高,并发量不大的系统

网站公告

今日签到

点亮在社区的每一天
去签到