Seata

什么是Seata

Seata , 官网 , github , 2.6多星

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下,提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案

在单体应用中,我们可以通过数据库的本地事务(如 MySQL 的 ACID)保证数据一致性,但微服务架构下,一个业务流程可能涉及多个服务(例如电商下单流程涉及订单服务、库存服务、支付服务),每个服务拥有独立的数据库,本地事务无法跨服务生效,此时就需要分布式事务框架协调各服务的事务,确保所有操作要么全部成功,要么全部回滚。

在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。商业化产品GTS 先后在阿里云、金融云进行售卖

Seata 的三大模块

Seata 架构由 Transaction Coordinator(TC)、Transaction Manager(TM)、Resource Manager(RM) 三大核心模块组成,三者协同工作实现分布式事务的生命周期管理,具体职责如下:

Seata的 TC、TM、RM 三个角色 , 和XA模型很像

Transaction Coordinator(TC):事务协调器

  • 核心定位:Seata 分布式事务的「大脑」,独立的中间服务,负责协调全局事务的提交或回滚,维护全局事务和分支事务的状态。
  • 核心职责:
    1. 接收 TM 发起的全局事务创建请求,生成全局唯一的 全局事务 ID(XID),并记录全局事务状态。
    2. 接收 RM 注册的分支事务(每个微服务的本地事务即为一个分支事务),关联分支事务与全局事务。
    3. 接收 TM 的全局提交 / 回滚指令,向所有关联的 RM 发送分支提交 / 回滚指令,协调所有分支事务达成最终一致性。
  • 部署特点:需独立部署(可集群部署保证高可用),与 TM、RM 通过网络通信(支持 TCP 等协议)。

Transaction Manager(TM):事务管理器

  • 核心定位:分布式事务的「发起者」和「决策者」,部署在业务应用的微服务中(通常是业务入口服务),负责定义全局事务的范围并触发全局提交 / 回滚。
  • 核心职责:
    1. 启动全局事务:在业务流程开始时,向 TC 发起创建全局事务的请求,获取 XID 并将其绑定到当前线程上下文,后续跨服务调用时会传递该 XID,用于关联分支事务。
    2. 决定事务结局:根据业务流程的执行结果(所有服务操作成功 / 部分服务失败),向 TC 发起全局提交或全局回滚指令。
  • 使用方式:通常通过注解(如 Seata 的 @GlobalTransactional)或 API 方式使用,开发者无需关注底层协调逻辑,只需标记全局事务范围。

Resource Manager(RM):资源管理器

  • 核心定位:分布式事务的「执行者」,部署在每个微服务中,负责管理本地数据库资源,执行分支事务的提交 / 回滚,并与 TC 通信同步分支事务状态。
  • 核心职责:
    1. 注册分支事务:微服务执行本地事务时,RM 会向 TC 注册该事务为当前全局事务的一个分支事务,关联 XID。
    2. 执行事务操作:接收 TC 下发的分支提交 / 回滚指令,执行本地事务的提交或回滚操作(例如 AT 模式下会通过 undo log 实现回滚)。
    3. 报告事务状态:向 TC 反馈分支事务的执行结果(成功 / 失败),以便 TC 掌握全局事务进度。
  • 适配范围:支持多种数据源(如 MySQL、Oracle 等关系型数据库),通过数据源代理等方式拦截 SQL 操作,实现事务的自动管理(如 AT 模式的无侵入式回滚)。

例如,在 Seata 的 AT 模式中,TM 和 RM 都作为SDK的一部分和业务服务在一起,我们可以认为是Client。TC是一个独立的服务,通过服务的注册、发现将自己暴露给Client们。

Seata 中有三大模块中, TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。

image-20251107093349981

而在 Seata 中,分布式事务的执行流程大概都是这样的

  • TM 开启分布式事务(TM 向 TC 注册全局事务记录);
  • 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
  • TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  • TC 汇总事务信息,决定分布式事务是提交还是回滚;
  • TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;

Seata的数据隔离性

Seata 作为分布式事务解决方案,针对不同的事务模式(AT、TCC、Saga、XA)提供了不同的数据隔离机制,以适应不同的业务场景和一致性要求。

AT 模式

AT 模式是 Seata 中使用最广泛的模式,它基于数据库的本地事务和 undo_log 日志实现分布式事务。

隔离性实现

  • 写隔离:在第一阶段,Seata 会对更新的数据行加行锁(通过数据库的行锁机制),直到全局事务提交或回滚才释放锁。
  • 读隔离:默认情况下,AT 模式的读操作不会加锁,可能导致脏读。如果业务需要强读隔离,可以通过 SELECT ... FOR UPDATE 显式加锁。

隔离级别

AT 模式的隔离级别默认是读已提交(Read Committed),可以通过配置调整为更高的隔离级别。

TCC 模式

TCC 模式(Try-Confirm-Cancel)需要业务方自己实现三个阶段的逻辑,因此隔离性的实现也由业务方控制。

隔离性实现

  • Try 阶段:通常会对资源进行预留(如冻结金额、锁定库存),这相当于一种乐观锁机制。
  • Confirm/Cancel 阶段:根据 Try 阶段的结果,决定是提交还是回滚预留的资源。

隔离级别

TCC 模式的隔离级别取决于业务方的实现,通常可以达到 ** 可重复读(Repeatable Read)** 级别。

Saga 模式

Saga 模式适用于长事务场景,通过正向服务和补偿服务的组合来保证最终一致性。

隔离性实现

Saga 模式本身不提供强隔离性,它通过最终一致性来保证数据的正确性。在 Saga 执行过程中,可能会出现中间状态的不一致,但最终会通过补偿操作恢复一致性。

隔离级别

Saga 模式的隔离级别是最终一致性,不提供传统数据库的隔离级别保证。

XA 模式

XA 模式基于传统的 XA 协议实现分布式事务,提供强一致性保证。

隔离性实现

XA 模式通过两阶段提交协议来保证隔离性:

  • 第一阶段(Prepare):所有参与的资源管理器(RM)对事务进行准备,此时资源被锁定。
  • 第二阶段(Commit/Rollback):根据第一阶段的结果,决定是提交还是回滚事务,释放资源锁。

隔离级别

XA 模式的隔离级别可以达到 ** 可重复读(Repeatable Read)甚至串行化(Serializable)** 级别,取决于底层数据库的支持。

Seata对四种分布式事务的解决方案

Seata 会有 4 种分布式事务解决方案,分别是 AT 模式、TCC 模式、Saga 模式和 XA 模式。

image-20251107093819561

Seata AT 模式

简介

Seata 的 AT 模式(Automatic Transaction,自动事务模式,自动化分支事务)是 Seata 中最常用的分布式事务模式之一,主打 无侵入式(对业务代码零侵入)和 自动补偿,适用于基于关系型数据库(如 MySQL、Oracle)的微服务场景。

总体来说,AT 模式,是 2pc两阶段提交协议的演变,不同的地方,Seata AT 模式不会一直锁表。

AT 模式基于 两阶段提交(2PC) 思想设计,Seata AT 模式是增强型 2pc 模式,或者说是增强型的XA模型,但通过对数据库操作的拦截和自动生成补偿日志(undo log),实现了事务的自动提交与回滚,简化了分布式事务的实现复杂度。

但是 AT 模式的使用存在限制,它仅支持关系型数据库,而且对 SQL 有一定限制(虽然目前支持大部分 DML 语句(INSERT/UPDATE/DELETE),但对复杂 SQL(如批量更新、子查询)的支持有限),而且高并发场景下,全局锁的竞争可能增加等待时间,需合理设计锁粒度(如避免大事务)。

核心流程可概括为:

  1. 第一阶段:各微服务执行本地事务并提交,同时生成 undo log(补偿日志)和 lock key(行锁标识),并向 TC 注册分支事务。
  2. 第二阶段:若所有分支事务成功,TC 协调全局提交,删除 undo log 和锁;若任一分支失败,TC 协调全局回滚,通过 undo log 反向补偿(回滚)本地事务。
image-20251107093906723

AT 模式的执行流程

结合上面的图片看,我们详细分析一下 Seata 的 AT 事务

首先,TM 发起全局事务启动

  • 当业务入口服务(TM 所在服务)的方法被 @GlobalTransactional 注解标记时,TM 向 TC 发起「创建全局事务」请求。
  • TC 生成唯一的 XID(全局事务 ID),并将 XID 返给 TM。并将 XID 绑定到当前线程上下文,后续跨服务调用时通过拦截器传递 XID,确保所有分支事务关联到同一全局事务。
  • 左上角的「Begin Global Transaction」虚线,代表 TM 与 TC 之间的全局事务初始化通信。

然后分支事务开始执行,进行本地事务提交和日志生成

每个参与的微服务都由 RM 管理本地资源,执行以下步骤:

  • 业务执行与本地事务提交
    • RM 内部的「Business」(Seata 中是通过数据源代理(如 DataSourceProxy))模块执行业务 SQL(如订单服务的 UPDATE、库存服务的 DELETE 等)。在执行前查询操作涉及的数据库行数据,作为「前镜像」(记录修改前的状态,用于回滚)。
    • RM 执行原业务 SQL,执行后立即提交本地事务(释放数据库本地锁,提升并发性能)。此时数据库数据已被修改,但事务最终状态需等待全局决策。
    • SQL 执行后,RM 再次查询操作涉及的数据库行数据,作为「后镜像」(记录修改后的状态)。
  • 生成 Undo Log 补偿日志
    • RM 拦截 SQL 操作,生成「前镜像」(修改前的数据)和「后镜像」(修改后的数据),并将这两个镜像及 SQL 信息封装成 Undo Log,写入数据库的 undo_log 表(图中两个「UNDO LOG」橙色模块即存储该日志)。
    • Undo Log 是回滚的核心依据,若后续需要回滚,可通过它将数据恢复到修改前的状态。
  • 注册分支事务并加全局锁
    • RM 向 TC 发起「注册分支事务」请求,携带 XID 和本地事务信息。
    • TC 记录分支事务与 XID 的关联,并为该分支事务涉及的数据库行分配 全局锁(通过 lock key 标识,防止其他分布式事务并发修改同一行数据)。
    • 图中「Register Branch」「Branch Status Report」的蓝色虚线,体现了 RM 与 TC 之间的分支事务注册和状态上报。

分支事务执行完之后,进行全局事务决策,在这里进行决定是全局提交或回滚

TC 根据所有分支事务的状态,协调最终的全局结果:

  • 情况 A:全局提交(所有分支成功)
    • TM 向 TC 发起「全局提交」指令。
    • TC 向所有 RM 发送「Branch Commit」指令。
    • RM 收到指令后,删除对应的 Undo Log(因为无需回滚),并释放全局锁。
    • 图中「Branch Commit」的蓝色虚线即该流程。
  • 情况 B:全局回滚(任一分支失败)
    • TM 向 TC 发起「全局回滚」指令。
    • TC 向所有 RM 发送「Branch Rollback」指令。
    • RM 收到指令后,读取 Undo Log 中的前镜像和后镜像,校验数据一致性后,执行反向 SQL(如 UPDATE 回滚为原数据、INSERT 对应 DELETE),完成本地事务回滚。
    • 回滚完成后,RM 删除 Undo Log 并释放全局锁。
    • 图中「Branch Rollback」的蓝色虚线即该流程。

从图中模块和流程可提炼出 AT 模式的两大核心设计,这也是它 “无侵入、高性能” 的关键:

传统 2PC 第一阶段会持有本地事务锁,这会导致并发性能差,这是前面说过的,但是,而 AT 模式在第一阶段就提交本地事务(RM 内的本地事务立即提交),仅通过 Undo Log 和全局锁保证后续回滚能力。这一设计大幅降低了数据库锁的持有时间,提升了并发性能。

而开发者也无需手动编写回滚逻辑,RM 会自动生成 Undo Log 并在回滚时反向执行 SQL。这种 “自动补偿” 机制让 AT 模式对业务代码零侵入(一般情况下只需加 @GlobalTransactional 注解基本就够了),大幅降低了开发成本。

而且其中全局锁是 Seata 维护的分布式锁,通过 lock key(由表名 + 主键组成,如 order:100)标识,防止在全局事务未完成(未提交 / 回滚)时,其他分布式事务修改同一行数据,导致回滚失败。

Seata 的 AT 模式主要实现逻辑是数据源代理,而数据源代理将基于如 MySQL 和 Oracle 等关系事务型数据库实现,基于数据库的隔离级别为 read committed。换而言之,本地事务的支持是 Seata 实现 AT 模式的必要条件,这也将限制 Seata 的 AT 模式的使用场景。

与数据库自身的事务行锁是如何协同的?这两者的锁是这样协同工作的

  • 分支事务执行时,RM 先获取本地锁,执行 SQL 后释放本地锁(因本地事务已提交),但会持有全局锁直到全局事务完成。
  • 其他事务若要修改同一行数据,需先申请全局锁,若全局锁被占用,则会阻塞等待(默认超时时间可配置),确保回滚时数据未被篡改。
对比维度 传统 2PC Seata AT 模式
本地事务状态 第一阶段不提交(持锁) 第一阶段提交(释放本地锁)
回滚依赖 需数据库支持 XA 协议 基于 undo log 自动回滚
侵入性 需手动处理分支事务 无侵入(注解 + 自动代理)
性能 锁持有时间长,性能低 第一阶段释放本地锁,性能更高

Seata AT模式的例子

https://www.cnblogs.com/crazymakercircle/p/13917517.html#autoid-h2-25-0-0

我们用一个比较简单的业务场景来描述一下Seata AT模式的工作过程。

有个充值业务,现在有两个服务,一个负责管理用户的余额,另外一个负责管理用户的积分。

当用户充值的时候,首先增加用户账户上的余额,然后增加用户的积分。

Seata AT分为两阶段,主要逻辑全部在第一阶段,第二阶段主要做回滚或日志清理的工作。

第一阶段流程:

第一阶段流程如

image-20251107111651311
  • 余额服务中的TM,向TC申请开启一个全局事务,TC会返回一个全局的事务ID。
  • 余额服务在执行本地业务之前,RM会先向TC注册分支事务。
  • 余额服务依次生成undo log、执行本地事务、生成redo log,最后直接提交本地事务。
  • 余额服务的RM向TC汇报,事务状态是成功的。
  • 余额服务发起远程调用,把事务ID传给积分服务。
  • 积分服务在执行本地业务之前,也会先向TC注册分支事务。
  • 积分服务次生成undo log、执行本地事务、生成redo log,最后直接提交本地事务。
  • 积分服务的RM向TC汇报,事务状态是成功的。
  • 积分服务返回远程调用成功给余额服务。
  • 余额服务的TM向TC申请全局事务的提交/回滚。

积分服务中也有TM,但是由于没有用到,因此直接可以忽略。

我们如果使用 Spring框架的注解式事务,远程调用会在本地事务提交之前发生。但是,先发起远程调用还是先提交本地事务,这个其实没有任何影响。

第二阶段流程如:

第二阶段的逻辑就比较简单了。

Client和TC之间是有长连接的,如果是正常全局提交,则TC通知多个RM异步清理掉本地的redo和undo log即可。如果是回滚,则TC通知每个RM回滚数据即可。

这里就会引出一个问题,由于本地事务都是自己直接提交了,后面如何回滚,由于我们在操作本地业务操作的前后,做记录了undo和redo log,因此可以通过undo log进行回滚。

由于undo和redo log和业务操作在同一个事务中,因此肯定会同时成功或同时失败。

但是还会存在一个问题,因为每个事务从本地提交到通知回滚这段时间里,可能这条数据已经被别的事务修改,如果直接用undo log回滚,会导致数据不一致的情况。

此时,RM会用redo log进行校验,对比数据是否一样,从而得知数据是否有别的事务修改过。注意:undo log是被修改前的数据,可以用于回滚;redo log是被修改后的数据,用于回滚校验。

如果数据未被其他事务修改过,则可以直接回滚;如果是脏数据,再根据不同策略处理。

Seata TCC 模式

简介

有人说,Seata TCC 模式 不是 前文所讲的 TCC 理论事务模型, 而是2PC二阶段提交的理论事务模型。

我认为虽然 Seata TCC 模式 属于强一致性, 不是弱一致性,但是 Seata TCC 所谓的二阶段是Try 是准备,Confirm/Cancel 是最终决策,这么划分的,2PC 的 Prepare 阶段是所有参与者锁定资源,Seata 的 TCC 的 Try 阶段,大部分情况,即使是我们需要自定义其逻辑,我们也需要为所有参与者预留资源,资源仍处于 “可用但不可重复预留” 状态,和 2PC 的锁定不算是一回事。

前面说了理论 TCC 和 Seata TCC 事务模型:

  • 理论上TCC事务模型:

    • Try-Confirm-Cancel,

    • 将分布式事务拆分为三个阶段,依赖业务代码自主实现事务的提交或回滚:

      • Try 阶段:资源检查与预留(比如冻结订单金额、锁定库存,确保后续操作可行)。
      • Confirm 阶段:确认执行(基于 Try 阶段的预留资源完成最终业务操作,如扣减金额、扣减库存,该阶段必须是幂等的)。
      • Cancel 阶段:取消执行,当事务失败时,释放 Try 阶段预留的资源,回滚到初始状态,同样需幂等。

      TCC理论模型 仅定义了三阶段行为规范和幂等、空回滚、防悬挂三大核心约束,不涉及具体的技术实现细节(如事务协调、状态管理、重试机制等)。

      image-20251107151956212
  • Seata TCC 事务模型

    在Seata中,AT模式与TCC模式事实上都是两阶段提交的具体实现,AT 模式基于 支持本地 ACID 事务的关系型数据库,而TCC 模式不依赖于底层数据资源的事务支持:

    • 一阶段 prepare 行为:调用自定义的 prepare 逻辑。
    • 二阶段 commit 行为:调用自定义的 commit 逻辑。
    • 二阶段 rollback 行为:调用自定义的 rollback 逻辑。

    简单点概括,SEATA的TCC模式就是手工的AT模式,由我们自己实现 prepare 、commit、rollback 方法,不依赖AT模式的undo_log。

优势

  • TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。

  • TCC性能更好,不必对数据加全局锁,允许多个事务同时操作数据。

缺点

  • TCC 是一种侵入式的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。
  • 每个阶段的数据操作都要自己进行编码来实现,事务框架无法自动处理。

Seata TCC 模式的执行流程

呃呃TCC的角色还是一样的,这个倒是没有变

  • TM(事务管理器):发起并协调全局事务,分两阶段驱动整个流程。
  • TC(事务协调器):维护全局事务和分支事务的状态,充当 TM 与 RM 之间的 “协调中枢”。
  • RM(资源管理器,TCC 模式下的服务化实现):负责具体资源的Try(准备)、Confirm(提交)、Cancel(回滚)操作,由业务编码实现。
image-20251107152643802

这是官网的图,TCC 模式,不依赖于底层数据资源的事务支持

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

第一阶段:全局事务开启与分支事务 “Try” 准备

  • 首先,TM 发起全局事务,业务入口服务(TM)向 TC 发起 “Begin Global Transaction” 请求,开启一个全局事务,TC 生成全局事务 ID。
  • 然后,分支事务注册与 Try 执行
    • TM 调用各业务服务(RM)的Try 方法(对应两阶段提交的 “Prepare” 阶段)。Try 方法负责资源检查(如校验库存、余额是否充足)和资源预留(如冻结库存、预留资金)。
    • 每个 RM 在执行 Try 后,向 TC 注册 “分支事务”(Register Branch),并上报自身 Try 的执行状态(Branch Status Report)。
  • 之后,TC 收集 Try 结果,TC 汇总所有分支事务的 Try 状态,判断是否全部成功。

来到二阶段,全局提交或全局回滚

  • 若所有分支 Try 都成功:

    TM 向 TC 发起 “Global Commit” 请求,TC 向所有参与的 RM 下发 “Branch Commit” 指令。

    各 RM 执行Confirm 方法(对应两阶段提交的 “提交” 阶段),执行真正的业务逻辑(如扣减库存、完成支付),使业务最终生效。

  • 若有任意分支 Try 失败:

    TM 向 TC 发起 “Global Rollback” 请求,TC 向所有参与的 RM 下发 “Branch Rollback” 指令。

    各 RM 执行Cancel 方法(对应两阶段提交的 “回滚” 阶段),取消 Try 阶段预留的资源(如解冻库存、释放预留资金),使资源回到初始状态。

Seata TCC 通过 “Try(一阶段准备)→ Confirm/Cancel(二阶段提交 / 回滚)” 的两阶段流程,结合 TM、TC、RM 的角色协作,保证了分布式场景下数据的最终一致性—— 要么所有分支事务都提交(Confirm),要么所有分支事务都回滚(Cancel),避免了数据不一致的情况。这也就是为什么,Seata TCC 模式 属于强一致性,而且是2PC二阶段提交的理论事务模型

Seata TCC 举例

我们以 “用户账户下单扣钱(商品价格 30 元,账户初始金额 100 元)” 为例,结合 Seata TCC 的Try-Confirm-Cancel机制,拆解一下其执行流程

那么这里对应的三个角色,分别就是

  • TM(事务管理器):发起下单请求的业务服务(如订单服务),负责开启全局事务、协调各分支事务。
  • TC(事务协调器):Seata 的 TC 模块,维护全局事务和分支事务状态,驱动二阶段提交 / 回滚。
  • RM(TCC 资源管理器):账户服务,需实现Try(冻结金额)、Confirm(提交扣钱)、Cancel(回滚冻结)三个方法。

首先,来到一阶段,全局事务开启然后 Try(资源预留)阶段

  • 订单服务(TM)向 TC 发起 “开启全局事务” 请求,TC 生成全局事务 ID
  • 订单服务调用账户服务的Try方法,首先,检查账户余额:100元 ≥ 30元,发现满足扣钱条件,然后会冻结金额freeze_amount = 0 + 30 = 30;那么他的可用金额money = 100 - 30 = 70,此时账户状态为money=70,freeze_amount=30
  • 然后账户服务(RM)向 TC 注册该分支事务,并上报Try执行成功的状态。

来到第二阶段,根据全局状态执行 Confirm 或 Cancel

  • 情况A:所有业务分支 Try 成功 → 执行 Confirm(提交扣钱)
    • 情况很好,看起来是对的,那么接下来,TC 汇总所有分支事务的Try状态,我去,均成功,通知 TM 可以提交全局事务。(TM:我去我去,TC神给我来电话了)
    • TM 向 TC 发起 “全局提交” 请求,希望执行接下来的业务,TC 向账户服务下发 “分支提交” 指令,告诉账户服务扣钱
    • 账户服务收到,执行Confirm方法,它会扣减冻结金额,freeze_amount = 30 - 30 = 0,因为Try阶段已完成扣减逻辑,最终可用金额就是 70,但是freeze_amount=0,业务完成
  • 情况B:任意业务分支 Try 失败 → 执行 Cancel
    • 情况不太妙,假如东西没了,库存服务的Try操作失败,TC 汇总分支事务状态时候发现了这个失败,它就会通知 TM 执行全局回滚。
    • TM 收到了通知之后,就会向 TC 发起 “全局回滚” 请求,TC 向账户服务下发 “分支回滚” 指令。
    • 账户服务执行Cancel方法,首先释放冻结金额freeze_amount = 30 - 30 = 0,恢复可用金额money = 70 + 30 = 100。此时账户状态money=100,freeze_amount=0,回到扣钱前的初始状态。
image-20251107161428108

可以看出,TryConfirmCancel三个方法需要由开发者针对 “账户扣钱” 场景自定义实现,但仅需主动调用Try方法ConfirmCancel由 Seata 框架根据全局事务状态自动触发。

Seata 的 Saga 模式

简介

前面我们其实已经对这个内容说的差不多了,Seata Saga 模式是 Seata 提供的长事务解决方案,其核心是将一个分布式事务拆分为多个 本地事务步骤,每个步骤对应一个独立的业务服务操作。当所有步骤正常执行时,事务成功完成;若某个步骤执行失败,则触发 补偿操作(Compensation Step),按照与正向步骤相反的顺序回滚已执行的本地事务,最终使整个系统回到一致状态。

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

其中,TC,TM,RM 这种关键角色,变成了

  • 事务发起者(TM 相关角色):触发 Saga 事务的启动,通常是业务入口服务。
  • Saga 协调器(Seata Saga 核心组件):负责编排 Saga 流程,管理各步骤的执行状态,在步骤失败时触发补偿流程,支持流程的暂停、恢复、重试等操作。
  • 业务服务(RM 相关角色):执行具体的本地事务步骤(正向操作)和补偿操作,每个业务服务需实现独立的正向方法和补偿方法。

好像差不多

Seata Saga 模式提供两种主流实现,适配不同的开发需求:

  • 编排式 Saga:通过编写流程定义(如 JSON、XML 或代码注解)描述正向步骤和补偿步骤的执行顺序,由 Saga 协调器按照流程定义驱动事务执行,灵活性高,支持复杂流程(分支、循环等)。
  • 注解式 Saga:通过注解(如 @SagaTask)标记正向方法和补偿方法,简化开发,适用于流程简单的场景,底层仍由协调器管理执行逻辑。
image-20251107201518468

这张图直观地展示了 Seata Saga 模式的核心执行逻辑,分为正常事务流程补偿事务流程两部分

正向步骤依次执行,若中途失败,反向补偿已成功的步骤,确保最终要么全成,要么全回滚

  • 左侧 “Normal transactions(正常事务)”

    绿色模块T1、T2、T3、Tn代表正向业务步骤,这些步骤会按顺序执行本地事务(比如创建订单、扣减库存、扣减余额等),每个步骤执行完成后立即提交并释放资源。

  • 右侧 “Compensating transactions(补偿事务)”

    红色模块C1、C2、C3代表反向补偿步骤。当某个正向步骤(如图中T3之后的步骤)执行失败时,Saga 模式会按照与正向步骤相反的顺序触发补偿操作,将之前成功的步骤(T3、T2、T1)依次回滚,最终保证系统数据的一致性。

Sega 模式的分布式事务适用于业务流程长、业务流程多的情况

优势是

  • 无锁设计,高吞吐量:无需像 2PC 或 TCC 那样持有锁或预留资源,每个本地事务执行完成后立即释放资源,适合长事务场景(如订单履约流程可能持续数小时)。
  • 适配弱一致性场景:不追求强一致性,而是通过补偿机制保证最终一致性,适配跨第三方系统(如支付、物流接口)的业务。
  • 流程灵活:支持复杂的业务流程编排(分支判断、循环执行、超时重试等),可通过流程定义动态调整事务步骤。
  • 低侵入性:业务服务仅需实现正向操作和补偿操作,无需改造原有业务逻辑的核心流程(相比 TCC 减少了代码侵入)。

缺点是

  • 补偿逻辑复杂:需为每个正向步骤设计对应的补偿操作,补偿逻辑需满足 “幂等性”(避免重复补偿导致数据异常)和 “可补偿性”(确保已执行的操作能被回滚)。
  • 无强一致性保障:在正向步骤执行完成到补偿操作触发的间隙,可能出现数据不一致的中间状态(需业务层面接受或通过其他方式规避)。
  • 依赖协调器可用性:Saga 协调器需保证高可用,否则可能导致事务流程中断(通常通过集群部署解决)。

Saga的实现

Seata Saga 模式通过状态机引擎来管理分布式事务的执行流程。状态机的核心是 “状态图”—— 它用可视化的节点和连线,定义了服务调用的顺序、补偿逻辑、分支判断等规则,并最终生成 JSON 格式的 “状态语言定义文件”。状态机引擎会解析这个文件,自动驱动事务的正向执行和异常补偿。

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
  3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
  4. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

其中,异常发生时是否进行补偿也可由用户自定义决定

Seata Saga 提供了一个可视化的状态机设计器方便用户使用,代码和运行指南请参考: https://github.com/apache/incubator-seata/tree/refactor_designer/saga/seata-saga-statemachine-designer

示例状态图:

image-20251107202131652

以图中的 “库存扣减 + 余额扣减” 场景为例

  • ReduceInventory 节点:代表 “扣减库存” 的正向服务。
    • 关联补偿节点 CompensateReduceInventory:若后续流程异常,会触发该节点执行 “恢复库存” 的补偿逻辑。
  • Choice 菱形节点:分支判断逻辑(如判断库存扣减是否成功)。
    • 若成功,流向 ReduceBalance 节点;
    • 若失败,流向 Fail 节点,触发全局补偿流程。
  • ReduceBalance 节点:代表 “扣减余额” 的正向服务。
    • 关联补偿节点 CompensateReduceBalance:若后续流程异常,触发 “恢复余额” 的补偿逻辑。
  • CompensationTrigger 节点:补偿流程的触发入口。
    • Fail 节点被触发时,会进入该节点,启动反向补偿流程(依次执行 CompensateReduceBalanceCompensateReduceInventory)。
  • Succeed 绿色节点:所有正向服务执行成功,事务完成。
  • Fail 红色节点:任意节点执行异常,触发补偿流程。

那么上述的场景,如果写成状态机的 JSON 就是这样

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{
"Name": "reduceInventoryAndBalance",
"Comment": "reduce inventory then reduce balance in a transaction",
"StartState": "ReduceInventory",
"Version": "0.0.1",
"States": {
"ReduceInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryAction",
"ServiceMethod": "reduce",
"CompensateState": "CompensateReduceInventory",
"Next": "ChoiceState",
"Input": ["$.[businessKey]", "$.[count]"],
"Output": {
"reduceInventoryResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
}
},
"ChoiceState": {
"Type": "Choice",
"Choices": [
{
"Expression": "[reduceInventoryResult] == true",
"Next": "ReduceBalance"
}
],
"Default": "Fail"
},
"ReduceBalance": {
"Type": "ServiceTask",
"ServiceName": "balanceAction",
"ServiceMethod": "reduce",
"CompensateState": "CompensateReduceBalance",
"Input": [
"$.[businessKey]",
"$.[amount]",
{
"throwException": "$.[mockReduceBalanceFail]"
}
],
"Output": {
"compensateReduceBalanceResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"Catch": [
{
"Exceptions": ["java.lang.Throwable"],
"Next": "CompensationTrigger"
}
],
"Next": "Succeed"
},
"CompensateReduceInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryAction",
"ServiceMethod": "compensateReduce",
"Input": ["$.[businessKey]"]
},
"CompensateReduceBalance": {
"Type": "ServiceTask",
"ServiceName": "balanceAction",
"ServiceMethod": "compensateReduce",
"Input": ["$.[businessKey]"]
},
"CompensationTrigger": {
"Type": "CompensationTrigger",
"Next": "Fail"
},
"Succeed": {
"Type": "Succeed"
},
"Fail": {
"Type": "Fail",
"ErrorCode": "PURCHASE_FAILED",
"Message": "purchase failed"
}
}
}

完全详细的”状态机”的属性列表和各种状态的属性列表如下:https://seata.apache.org/zh-cn/docs/user/mode/saga/#state-language-reference

我这里只说”状态机”的属性列表的了:

  • Name: 表示状态机的名称,必须唯一
  • Comment: 状态机的描述
  • Version: 状态机定义版本
  • StartState: 启动时运行的第一个”状态”
  • States: 状态列表,是一个 map 结构,key 是”状态”的名称,在状态机内必须唯一, value 是一个 map 结构表示”状态”的属性列表
  • IsRetryPersistModeUpdate: 向前重试时, 日志是否基于上次失败日志进行更新, 默认是 false, 即新增一条重试日志 (优先级高于全局 stateMachineConfig 配置属性)
  • IsCompensatePersistModeUpdate: 向后补偿重试时, 日志是否基于上次补偿日志进行更新, 默认是 false, 即新增一条补偿日志 (优先级高于全局 stateMachineConfig 配置属性)

注意,saga 一般有两种实现,一种是基于状态机定义,比如 apache camel saga、eventuate,一种是基于注解+拦截器实现,比如 serviceComb saga,后者是不需要配置状态图的。

但是由于 Saga 事务不保证隔离性, 在极端情况下可能由于脏写无法完成回滚操作,这时则没有办法进行补偿了,此时 基于状态机引擎可以提供“回滚”能力外,还可以提供“向前”恢复上下文继续执行的能力,让业务最终执行成功,,达到最终一致性的目的,允许让业务最终成功, 在回滚不了的情况下可以继续重试完成后面的流程。

所以在实际生产中基于状态机的实现应用更多

而且,seata saga 开启事务的客户端或者 seata server 服务端宕机或者重启,未完成的状态机实例是靠状态机实例在本地数据库有记录日志,通过日志恢复的。seata server 会触触发事务恢复。

Seata 的 XA 模式

简介

XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

Seata 的 XA 模式是基于经典的 XA 分布式事务协议实现的一种分布式事务解决方案,它遵循了 XA(eXtended Architecture)的两阶段提交协议,为分布式系统提供了强一致性的事务保障。

经典的 XA 模式在前面已经详细的说明过了,而在 Seata 的 XA 模式中:

  • 事务管理器(TM):由 Seata Server 充当,负责全局事务的提交和回滚决策。
  • 资源管理器(RM):由各个参与分布式事务的数据库(如 MySQL、Oracle 等)充当,负责管理本地事务,并与 TM 通信。

这貌似没什么区别

使用 Seata XA 模式是有前提的

  • 支持XA 事务的数据库。
  • Java 应用,通过 JDBC 访问数据库。

所以与 Seata 支持的其它事务模式不同,XA 协议要求事务资源本身提供对规范和协议的支持,所以事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。此外的一些优势还包括:

  1. 业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
  2. 数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。

缺点也和 XA 模式的一样,XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,性能差。

Seata XA 模式的整体机制

在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

image-20251107203935950

Seata XA 模式严格遵循XA 协议的两阶段提交

阶段一:全局事务启动 和 分支事务Prepare

  1. TM 发起全局事务:TM 向 TC 发送 Begin Global Transaction 请求,TC 生成全局事务 ID,标记事务开始。
  2. RM 注册分支事务:每个参与的 RM(如订单库、库存库)向 TC 发送 Register Branch 请求,注册自己的分支事务。
  3. RM 执行本地 “准备” 操作:
    • RM 执行 XA Start 开启本地分支事务,执行业务 SQL(如 “创建订单”“扣减库存”)。
    • 执行 XA Prepare:将本地事务置为 “准备提交” 状态(此时事务已执行但未提交,资源被锁定),并向 TC 上报 Branch Status Report(告知准备结果)。

阶段二:全局提交或回滚

  • 若所有 RM 准备成功:

    TC 向所有 RM 发送Branch Commit指令,RM 执行XA Commit提交本地事务,释放资源。

  • 若任意 RM 准备失败:TC 向所有 RM 发送Branch Rollback指令,RM 执行XA Rollback回滚本地事务,恢复数据一致性。

和 XA 协议的 2pc 差不多,Seata XA 模式的本质是通过 TC 协调,让所有参与的 RM 先 “准备”(保留提交能力但不实际提交),再由 TC 统一决策 “提交” 或 “回滚”,从而保证分布式事务的强一致性

正式这种机制才导致完全遵循 XA 协议,兼容性强

image-20251107204911207

那么所谓的数据源代理是什么呢?

在 Seata 中,数据源代理(DataSource Proxy) 是实现分布式事务拦截的核心组件。它的作用是对原始数据库连接(DataSource)进行包装,在 SQL 执行的生命周期中插入 Seata 的事务逻辑(如分支事务注册、XA 协议的 Prepare/Commit 等)。

  • 对于AT 模式(左半部分黄色组件):DataSourceProxy 包装原始 DataSourceConnectionProxy 包装原始 ConnectionStatementProxy 包装原始 Statement。通过这三层代理,Seata 可以拦截 SQL 执行,自动生成 undo_log 实现回滚。
  • 对于XA 模式(右半部分蓝色组件):DataSourceProxyXA 是专门适配 XA 协议的数据源代理,它会进一步包装出 XAConnection(支持 XA 两阶段提交的数据库连接),并通过 ConnectionProxyXAStatementProxyXA 实现 XA 协议的流程控制。

首先,XA 模式需要 XAConnection。

XAConnection 是什么呢?XAConnection支持 XA 分布式事务协议的数据库连接,它是实现 XA 模式的底层核心对象

XAConnection 不仅具备普通数据库连接(Connection)的所有能力,还额外提供了 XA 协议所需的接口,就这三个阶段

  • XAStart():开启一个 XA 分支事务。
    • XA start 需要 Xid 参数。这个 Xid 需要和 Seata 全局事务的 XID 和 BranchId 关联起来,以便由 TC 驱动 XA 分支的提交或回滚。
    • 目前 Seata 的 BranchId 是在分支注册过程,由 TC 统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前。
  • XAPrepare():执行 XA 第一阶段(准备阶段)。
  • XACommit()/XARollback():执行 XA 第二阶段(提交 / 回滚阶段)。

所以和之前说的似的,只有数据库驱动和数据库本身支持 XAConnection,才能参与 Seata 的 XA 分布式事务。例如,MySQL 5.7+、Oracle、DB2 等主流数据库都提供了对 XAConnection 的支持。

获取 XAConnection 两种方式:

  • 方式一:要求开发者配置 XADataSource
  • 方式二:根据开发者的普通 DataSource 来创建

方式一过于反人类,一般情况下实现第二种方式,数据源代理根据普通数据源中获取的普通 JDBC 连接创建出相应的 XAConnection。

而XA 模式使用起来与 AT 模式基本一致,用法上的唯一区别在于数据源代理的替换

Seata 新功能: 和Rocketmq 事务消息实现,实现 强弱结合型事务

简介

众所周知,四大天王有五个)

这次算是把拼图拼好了,这对应了之前我们讲的通知型事务的使用支持半消息的消息队列进行的分布式事务的实现

强一致性对应的就是 Seata,弱一致性对应的就是 MQ

RocketMQ 的事务消息能够保证本地操作 + 消息发送的原子性。具体来说,主要是保证了本地方法执行和消息发送在一个分布式事务中,要不全部成功,要不全部失败。

Seata 与 RocketMQ 事务消息的结合,本质是强一致性方案(Seata 核心事务)最终一致性方案(RocketMQ 事务消息) 的互补融合。这种 “强弱结合” 模式既保留了核心业务的强一致性保障,又通过消息队列解决了非核心业务的异步解耦问题,完美适配 “核心流程强一致 + 扩展流程最终一致” 的复杂业务场景

在实际业务中,纯粹的强一致性(如 Seata XA/TCC)或纯粹的最终一致性(如 RocketMQ 事务消息)都存在局限性,你要是强一致,必然所有步骤需同步执行,资源锁定时间长,并发性能低,而且沟槽的XA难以适配跨第三方系统的异步场景,你要是最终一致性,就依赖消息重试和补偿机制,无法保证核心业务数据的实时一致性

因此,“强弱结合” 模式的核心思路是:

  1. 核心链路强一致:将影响数据正确性的关键步骤(如订单创建、库存扣减、余额支付)通过 Seata 实现强一致性,避免业务异常。

  2. 扩展链路最终一致:将非核心但需后续执行的步骤(如积分发放、物流单生成、消息通知)通过 RocketMQ 事务消息异步触发,提升系统并发能力。、

Seata + Rocketmq 事务消息 结合

Seata + Rocketmq 事务消息 结合的目标是:

  • 保证 分布式事务 + 消息 发送的原子性。
  • 通过mq的重试机制,去保证订阅者的最终一致性

RocketMQ 事务消息的核心机制是它通过 “半消息 + 本地事务确认” 保证本地操作与消息发送的原子性,具体流程如下:

  1. 发送半消息:生产者向 RocketMQ 发送 “半消息”(Half Message),此时消息被标记为 “不可投递” 状态,消费者无法消费。
  2. 执行本地事务:生产者执行核心本地业务逻辑(如 Seata 管理的强一致性事务)。
  3. 确认消息状态:
    • 若本地事务执行成功,生产者向 RocketMQ 发送Commit指令,消息变为 “可投递” 状态,消费者正常消费。
    • 若本地事务执行失败,生产者向 RocketMQ 发送Rollback指令,消息被删除,消费者无法感知。
  4. 事务回查:若 RocketMQ 未收到生产者的确认指令(如生产者宕机),会定时发起 “事务回查”,生产者需根据本地事务日志返回最终状态,确保消息状态一致。

这样,RocketMQ 事务消息的核心价值是解决了 “本地事务执行” 与 “消息发送” 的原子性问题,避免出现 “本地事务成功但消息未发送” 或 “消息发送成功但本地事务失败” 的不一致场景。

结合模式的核心是将 Seata 管理的强一致性事务作为 RocketMQ 事务消息的 “本地事务”,通过两者的协同实现端到端的事务一致性。以下是具体实现流程和架构设计:

image-20251107205445573
  • 事务协调层:Seata Server(TC)负责协调核心链路的强一致性事务,RocketMQ 负责异步消息的投递与状态管理。
  • 业务层:
    • 核心服务(订单、库存、支付):通过 Seata 参与分布式事务,保证强一致性。
    • 扩展服务(积分、物流、通知):通过消费 RocketMQ 消息实现异步处理,保证最终一致性。
  • 存储层:核心业务数据存储在支持 Seata 的数据库中,消息状态存储在 RocketMQ 集群中,本地事务日志用于状态回查。

那么整体的实现流程可以这样的剖析:

以电商下单为例

步骤 1:发起全局事务与半消息发送
  • 订单服务作为 TM,调用 Seata API 发起全局事务(GlobalBegin),获取全局事务 ID。
  • 订单服务向 RocketMQ 发送 “积分发放 + 物流创建” 的半消息,携带全局事务 ID 作为关联标识。
步骤 2:执行 Seata 强一致性核心事务
  • 订单服务执行本地事务(创建订单),并通过 Seata 调用库存服务(扣减库存)、支付服务(扣减余额)。
  • 各核心服务的 RM 向 Seata TC 注册分支事务,执行Prepare操作(XA 模式)或直接执行本地事务并记录 undo_log(AT 模式)。
  • 若所有核心服务执行成功,Seata TC 下达GlobalCommit指令,各 RM 提交本地事务;若任意步骤失败,TC 下达GlobalRollback指令,各 RM 回滚事务。
步骤 3:确认 RocketMQ 消息状态
  • 若 Seata 全局事务提交成功:订单服务向 RocketMQ 发送Commit指令,半消息变为可投递状态,积分服务、物流服务消费消息并执行对应业务。
  • 若 Seata 全局事务回滚失败:订单服务向 RocketMQ 发送Rollback指令,半消息被删除,扩展服务无感知,避免数据不一致。
步骤 4:异常处理与回查机制
  • 核心事务异常:Seata 触发回滚后,消息直接回滚,无需后续处理。
  • 消息确认超时:RocketMQ 发起事务回查,订单服务通过查询 Seata 全局事务状态(已提交 / 已回滚),向 RocketMQ 返回最终确认指令。
  • 消息消费失败:RocketMQ 支持重试机制,若重试失败则进入死信队列,由人工干预或定时任务处理,确保最终一致性。

Seata + Rocketmq 事务消息 结合中,Seata 的改进主要在 prepare 阶段。

Seata 提供了一个 SeataMQProducer 类,把 RocketMQ 中 TransactionListener 的方法加入到全局事务。

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
SeataMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
super(namespace, producerGroup, rpcHook);
this.transactionListener = new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
return LocalTransactionState.UNKNOW;
}

@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String xid = msg.getProperty(PROPERTY_SEATA_XID);
if (StringUtils.isBlank(xid)) {
LOGGER.error("msg has no xid, msgTransactionId: {}, msg will be rollback", msg.getTransactionId());
return LocalTransactionState.ROLLBACK_MESSAGE;
}
GlobalStatus globalStatus = DefaultResourceManager.get().getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, xid);
if (COMMIT_STATUSES.contains(globalStatus)) {
return LocalTransactionState.COMMIT_MESSAGE;
} else if (ROLLBACK_STATUSES.contains(globalStatus) || GlobalStatus.isOnePhaseTimeout(globalStatus)) {
return LocalTransactionState.ROLLBACK_MESSAGE;
} else if (GlobalStatus.Finished.equals(globalStatus)) {
LOGGER.error("global transaction finished, msg will be rollback, xid: {}", xid);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.UNKNOW;
}
};

在 prepare 阶段,SeataMQProducer 向 RocketMQ Broker 发送 half 消息,执行本地事务,如果执行成功,则强行把 LocalTransactionState 改回 UNKNOW,等待 TC 发送指令,决定是 commit 或 rollback。

如果执行失败,返回 ROLLBACK_MESSAGE,TC 下发指令,回滚全局事务。