文章目录
一、Seata 框架概述
Seata(Simple Extensible Autonomous Transaction Architecture)是一款开源的分布式事务解决方案,其愿景是让分布式事务的使用像本地事务一样简单和高效。
它的核心思想是:通过一个全局事务来协调和管理多个分支事务(即本地事务)的提交和回滚,从而保证全局数据的一致性。在微服务架构中,业务逻辑通常需要跨多个服务、多个数据库,Seata 正是为了解决由此产生的分布式事务问题而生的。
二、核心功能特性
多事务模式支持:这是 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 通知所有分支提交或回滚。 - 优点:强一致性,事务隔离性好。得到了多数主流数据库的支持。
- 缺点:资源锁定时间长,在第二阶段完成前,所有资源都被锁定,性能较差。
- AT 模式(默认, Automatic Transaction):
高可用(High Availability):TC(事务协调器)支持基于注册中心(如 Nacos、Eureka)和配置中心(如 Apollo、Nacos)的集群部署,避免单点故障。
开箱即用:提供了丰富的配置选项,与主流的微服务框架(Spring Cloud、Dubbo)、注册中心、配置中心都有很好的集成。
丰富的生态:与多家云厂商和开源项目做了适配和集成。
三、整体架构与三大角色
Seata 的架构中包含了三个核心角色,其交互关系如下图所示:
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 模式为例)
假设一个下单流程:订单服务
-> 库存服务
-> 账户服务
。
- TM 开启全局事务:
- 用户调用订单服务的“创建订单”接口。
- 订单服务中的 TM(被
@GlobalTransactional
注解的方法)向 TC 申请开启一个全局事务(XID),TC 会生成一个唯一的 XID 并返回。
- RM 注册分支事务:
- 订单服务执行本地事务,在向订单表插入数据前,RM 会先拦截 SQL,生成查询快照和回滚日志(
undo_log
),并写入数据库。 - 订单服务的 RM 向 TC 注册一个分支事务,并将这个分支事务与之前的 XID 关联。
- 订单服务提交本地事务。
- 订单服务执行本地事务,在向订单表插入数据前,RM 会先拦截 SQL,生成查询快照和回滚日志(
- 调用链传播 XID:
- 订单服务通过 Feign/Ribbon 调用库存服务。Seata 会自动将 XID 通过请求头(例如
tx-seata-xid
)传递到下游服务。
- 订单服务通过 Feign/Ribbon 调用库存服务。Seata 会自动将 XID 通过请求头(例如
- 下游服务执行:
- 库存服务收到请求,其 RM 也感知到了这个 XID。
- 库存服务执行扣减库存的本地事务,同样生成
undo_log
并向 TC 注册属于自己的分支事务,然后提交本地事务。 - 账户服务同理。
- 全局提交或回滚:
- 成功情况:所有分支事务都成功执行并注册。TM(订单服务)会向 TC 发起全局提交的指令。TC 会异步地通知所有 RM 删除各自的
undo_log
日志。整个过程非常快,因为一阶段已经提交了。 - 失败情况:如果账户服务扣款失败,它会抛出异常。这个异常会沿着调用链向上传播,最终被订单服务中的 TM 捕获。
- TM 会向 TC 发起全局回滚的指令。
- TC 根据 XID 找到所有相关分支事务,并向它们的 RM 下达回滚命令。
- 各个 RM 收到回滚命令后,根据本地数据库中的
undo_log
日志,生成反向的 SQL 语句(例如 INSERT 变成 DELETE,UPDATE 变回旧值)并执行,完成数据回滚,最后删除undo_log
。
- 成功情况:所有分支事务都成功执行并注册。TM(订单服务)会向 TC 发起全局提交的指令。TC 会异步地通知所有 RM 删除各自的
总结
特性/模式 | AT 模式 | TCC 模式 | Saga 模式 | XA 模式 |
---|---|---|---|---|
侵入性 | 低(无侵入) | 高(需实现接口) | 中(需定义补偿) | 低(无侵入) |
性能 | 较好 | 非常好 | 非常好(可异步) | 差(资源锁定) |
隔离性 | 读未提交(靠全局锁) | 依赖业务实现 | 无隔离 | 强隔离 |
适用场景 | 大部分场景,希望简单高效 | 高性能要求,金融支付 | 长事务,业务流程多的系统 | 传统银行、强一致性需求 |
Seata 通过其清晰的角色划分和对多种模式的支持,为复杂的分布式系统环境提供了灵活而强大的一致性保证,是构建现代微服务架构不可或缺的重要组件。
五、一个大比喻:Seata 就像一次「跨银行转账」
想象一下,你要从【建设银行】的账户转 100 块钱到你的朋友在【工商银行】的账户。这个操作其实就是一个典型的分布式事务,它涉及了两个不同的银行系统(两个独立的数据库)。
Seata 的三大角色在这场“转账”中是谁?
TC (事务协调器) - 总行清算中心
- 它是谁:一个独立的核心机构,不属于任何一家地方银行。它知道全国所有跨行交易的进展。
- 干什么活:
- 记录每一笔跨行转账的全局状态(开始了?成功了?失败了?)。
- 负责最终下达命令:“所有银行,请最终确认提交!” 或者 “所有银行,立刻撤销刚才的操作!”
- 特点:必须非常可靠,所以通常是集群部署(好几个备份清算中心),防止一个挂了导致全国转账瘫痪。
TM (事务管理器) - 建设银行的那个柜员/ATM机
- 它是谁:发起整个事务的人。就是你面对的那个帮你操作转账的柜员,或者那台ATM机。
- 干什么活:
- 他/它先向“总行清算中心(TC)”汇报:“您好,我要开始一笔从建行转工行的转账了,编号是XXX”。
- 如果整个流程顺利,他/它就向TC发起“最终提交”的指令。
- 如果中途出错(比如工行账户不存在),他/它就立刻向TC发起“回滚”的指令。
RM (资源管理器) - 建设银行和工商银行自己的账本系统
- 它是谁:每个参与事务的资源个体。在这里就是【建设银行】的数据库和【工商银行】的数据库。
- 干什么活:
- 建行RM:负责从你的账户上临时扣下100块(但先不真正给别人)。
- 工行RM:负责在你朋友的账户上临时加上100块(但先不让他用)。
- 它们都要向“总行清算中心(TC)”汇报:“我这边临时操作完了,状态OK,随时等待您的最终命令”。
- 最后听从TC的命令,要么把“临时”变成“正式”,要么撤销刚才的临时操作。
六、Seata 的四种模式就是四种不同的转账策略
现在,总行清算中心(TC)提供了几种不同的转账流程供你选择:
1. AT模式(自动挡模式) - 「预授权」转账
这是最常用、最省心的模式,就像你用信用卡的预授权。
流程:
- 第一阶段:
- 建行RM:先从你账户冻结100块(就像酒店冻结你信用卡一定额度,钱还没划走,但你也不能用了)。
- 工行RM:在你朋友账户里先标记有100块即将到账(但还不能用)。
- 两边都向TC报告:“预授权成功”。
- 第二阶段:
- 如果成功:TC说“确认”,两家银行就把预授权变成实际扣款和实际入账。
- 如果失败:TC说“撤销”,建行就解冻你的100块,工行就取消那100块的标记。
- 第一阶段:
优点:你(程序员)几乎无感,Seata帮你自动搞定了一切,就像开车用自动挡。
缺点:用了“全局锁”(即冻结资金),性能有一点点损失。
2. TCC模式(手动挡模式) - 「自己打电话确认」转账
这个模式要求高,需要你(业务开发者)自己介入更多,就像大额转账需要你亲自打电话一步步确认。
流程:
- Try (尝试)阶段:你自己打电话。
- 打给建行:“麻烦你帮我试试看能不能扣100块?”(资源检查和预留)。
- 打给工行:“麻烦你试试看能不能收100块?”。
- Confirm (确认)阶段:如果两边都回电话说“没问题”,你就再打一次电话:“好,请正式操作!”
- Cancel (取消)阶段:如果工行说“账户不对,收不了”,你就得再打给建行:“刚才的尝试作废,解封吧。”
- Try (尝试)阶段:你自己打电话。
优点:性能最好,控制力最强,没有全局锁。
缺点:太麻烦!你需要写(打)很多代码(电话)来实现Try、Confirm、Cancel三个操作。
3. Saga模式 - 「先垫付,后报销」模式
适用于一个非常长的链条,比如公司组织团建,你先自己垫钱,最后统一报销。
流程:
- 你先自己掏钱买了机票(提交本地事务)。
- 你又自己掏钱订了酒店(提交本地事务)。
- 最后你申请公司报销。
- 如果报销成功:完美!
- 如果报销失败:公司不认这笔钱。那么你就得反向操作:把机票退掉、把酒店退掉(补偿操作)。
优点:吞吐量高,适合长流程业务。
缺点:可能你退机票的时候发现已经不能退了(缺乏隔离性),就得自己承担损失。
4. XA模式 - 「柜台排队,同时盖章」模式
传统银行的老做法,非常严格但效率低。
流程:
- 建行和工行的柜员都拿到了你的转账单。
- 他们互相打电话确认:“我准备好了,你呢?”“我也准备好了。”
- 等到两边都确认准备好后,在同一时刻一起盖章生效。
优点:强一致性,最安全。
缺点:在准备和确认的过程中,你账户的钱和对方账户的钱都被锁住,不能做任何其他操作,效率最低。
总结一下
- 想省心,就用 AT模式(默认),像开自动挡汽车。
- 追求极致性能和高控制,不怕麻烦就用 TCC模式,像开手动挡赛车。
- 业务流程特别长,就用 Saga模式,像先垫付后报销。
- 传统、强一致的系统(如银行核心系统),会用 XA模式,像老式的柜台同步操作。
所以,Seata 本质上就是一个分布式事务的调度中心,它提供了好几种“工作流程”来保证:要么大家都成功,要么都失败,绝不会出现“我的钱扣了,对方却没收到”的尴尬局面。
好的,我们接着用比喻和实例的方式,来深入讲解如何实现 Seata 的这几种模式。
七、如何实现 AT 模式(自动挡模式)
AT 模式是 Seata 的默认模式,对代码侵入性极低,就像开自动挡汽车。
实现步骤(基于 Spring Cloud + Spring Boot):
“考驾照” - 引入依赖与配置
首先,你需要在你的微服务项目中“考取”操作 Seata 的资格。<!-- 在您的 order-service, storage-service, account-service 的 pom.xml 中 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
“给车加油和设置导航” - 配置 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
“创建交通规则” - 建立 undo_log 表
AT 模式的核心是回滚日志。你需要在每一个业务涉及的数据库中,都创建一张undo_log
表。这张表就像是汽车的“黑匣子”,记录了数据修改前的样子,以便出问题时能还原。
(建表 SQL 在 Seata 官方文档的脚本里可以找到)“握紧方向盘,踩下油门” - 使用全局事务注解
现在,你只需要在发起全局事务的入口方法上,加一个神奇的注解@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 模式需要你亲自编写业务逻辑,实现“尝试”、“确认”、“取消”三个操作,就像开手动挡车。
实现步骤:
“定义三个踏板” - 编写 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); }
“制造三个踏板” - 实现 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; } }
“挂挡给油” - 在业务中调用 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 模式通常用于业务流程非常长的场景,比如一个旅游预订流程,包含订机票、订酒店、办签证等多个步骤,每个步骤都可能耗时很久。
实现方式(状态机模式):
- 定义状态机JSON:你需要用一个 JSON 文件来详细定义整个业务流程的每一个步骤(
State
),以及每个步骤失败后对应的补偿操作(Compensation
)。 - 部署状态机:将这个 JSON 文件部署到 Seata 的 Saga 状态机引擎中。
- 触发执行:你的程序只需要发起一个请求,状态机引擎就会自动按照你定义的流程一步步执行,并在任何一步失败时,自动反向执行之前所有成功步骤的补偿操作。
特点:
- 一阶段就提交:每个服务都直接提交本地事务,所以没有锁,性能高。
- 全靠补偿:一致性靠的是补偿操作,如果补偿操作也失败了,就需要人工介入处理。不保证隔离性。
适用场景:电商订单履约、物流系统、工作流审批等长时间运行的业务流程。
2. XA 模式(传统强一致模式)
XA 模式是数据库层面提供的标准协议,Seata 也提供了支持。
实现步骤:
切换模式:在你的
application.yml
中,将数据源代理模式改为 XA。seata: data-source-proxy-mode: XA # 默认是 AT
使用注解:和 AT 模式一样,在事务入口使用
@GlobalTransactional
注解。
背后原理:
- 一阶段:Seata 会调用
XA Start
、XA End
、XA Prepare
。此时,数据库会执行 SQL,但并不提交,而是将事务置于“准备就绪”状态,数据对其他事务不可见。 - 二阶段:
- 提交:TC 通知所有 RM
XA Commit
,所有数据库同时提交。 - 回滚:TC 通知所有 RM
XA Rollback
,所有数据库同时回滚。
- 提交:TC 通知所有 RM
特点:
- 强一致性:在整个事务结束前,数据都被锁住,完美保证隔离性。
- 性能差:因为资源锁定时间长,并发性能是几种模式中最差的。
适用场景:传统金融、银行等对强一致性要求极高,且并发量不是天文数字的内部系统。
总结与选择
模式 | 实现关键 | 比喻 | 适用场景 |
---|---|---|---|
AT | 加注解 @GlobalTransactional |
自动挡 | 绝大部分场景,追求简单和效率 |
TCC | 自己写 try 、confirm 、cancel 三个方法 |
手动挡 | 高性能、高并发场景,如资金交易 |
Saga | 用状态机JSON定义流程和补偿 | 先做后报 | 长流程业务,如订单履约、工作流 |
XA | 配置 data-source-proxy-mode: XA + 加注解 @GlobalTransactional |
同步盖章 | 传统行业,强一致性要求极高,并发量不大的系统 |