跳至正文
来两杯美式
返回

分布式事务五种方案:消息驱动 / XA / TCC / 本地消息表 / Saga

By 来两杯美式
发布于更新于

单体里的一个 @Transactional 就能搞定的事,到了微服务就变成头号难题——跨库、跨服务、跨进程,本地事务管不到。本篇按「强一致 → 最终一致」的脉络,梳理五种主流分布式事务方案。

一、问题背景与理论基础

1.1 为什么会有分布式事务

原始业务场景:用户下单 → 锁票 → 创建订单 → 扣费 → 交票。在单体里,这是一个 @Transactional 包住的本地事务。一旦拆成微服务:

1.2 CAP 与 BASE

CAP 与 BASE 是分布式事务的理论基石,本节只做指针——完整推导见 CAP 与 BASE:分布式系统的理论基石

理论含义实践取舍
CAP一致性 / 可用性 / 分区容错 三选二分布式系统 P 必选,剩 C vs A
BASEBasically Available + Soft state + Eventually consistent牺牲强一致,换取可用性 + 最终一致

分布式事务的所有方案,本质都是在 C 和 A 之间做取舍

  • 强一致(CP):XA / 2PC——锁资源、效率低,但数据严格一致
  • 最终一致(AP):消息驱动 / TCC / 本地消息表 / Saga——可用性好,但有短暂不一致窗口

二、方案一:消息驱动(可靠消息最终一致性)

2.1 整体流程

消息驱动分布式事务:MQ + 状态机推进,Ticket/Order/User 服务消费消息并投递下一状态

核心思想:把一条业务流程拆成多个消息驱动的步骤,每个服务消费消息后处理本地事务,并向 MQ 投递下一状态消息。

新订单 ──► [Ticket 锁票] ──► 订单创建 ──► [Order 创建订单] ──► 待缴费

   ◄── [Order 完成订单] ◄── 交票 ◄── [Ticket 交票] ◄── [User 扣费] ◄┘

2.2 强一致性的丧失

整条业务线无法实现强一致性。如走到第三步:

→ 这就是「短暂不一致窗口」,需要业务设计上容忍(如「出票中」状态、超时查询、客服兜底)。

2.3 对 MQ 的要求

2.4 失败处理策略

对执行过程中小模块失败的情况,要做处理:

2.5 简单实现的代码与缺陷

每个模块内服务的调用,最朴素的写法:

public void trans() {
    try {
        // 1. 操作数据库
        boolean result = dao.update(data);  // 操作数据库失败,会抛出异常
        // 2. 如果数据库操作成功则发送消息
        if (result) {
            mq.send(data);  // 如果方法执行失败,会抛出异常
        }
    } catch (Exception e) {
        rollback();  // 如果发生异常,就回滚
    }
}

服务 A 的伪代码:先 dao.update 再 mq.send,简单 try-catch 回滚

这个写法存在严重缺陷

核心矛盾:DB 和 MQ 是两个独立资源,不能用本地事务同时管。这就是后面「本地消息表」要解决的问题。

2.6 异步事件通知——外部事件服务

改进方案:使用异步事件通知——引入外部事件服务。

事件驱动时序图:微服务 A 先持久化事件,提交本地事务后再通知 MQ 投递给 B

时序图关键步骤:

步骤动作
1-2微服务 A 开启事务、更新 DB
3向消息代理投递事件(消息代理先持久化事件,不立即发送
4事件投递成功回执
5消息代理异步投递给微服务 B
6微服务 A 提交本地事务
7微服务 B 处理完成回执

关键设计:DB 处理业务提交前,向 MQ 发送事件,MQ 只记录事件,并不发送;当 DB 提交或回滚后,再通知 MQ 发送或删除事件。

为防止业务通知事件「发送或删除」时故障,另外起一套定时任务监听 MQ 队列中的消息,将消息去 DB 业务查询该事件是否需要发送或删除。

缺点

三、方案二:XA 协议的两阶段提交(2PC)

3.1 协议规范

XA 协议是 X/Open 组织提出的分布式事务规范:

3.2 第一阶段:prepare

XA 第一阶段:TM 向所有 RM 发 prepare,各 RM 返回 ready

3.3 第二阶段:commit / rollback

XA 第二阶段:TM 向所有 RM 发 commit,各 RM 返回 committed

3.4 特点

维度评价
一致性✅ 保证数据库的强一致性
可用性❌ 任一 RM 故障,整个事务阻塞
性能❌ 性能与本地事务比相差 10 倍(资源锁定时间长)
异常commit 阶段部分 RM 出现问题,事务不一致,需要手动介入人工处理

3.5 使用

现代视角(2026)补一句:XA 在 2019 年已经是「能不用就不用」的方案——性能太差、协调者单点问题、commit 阶段异常难以自动恢复。今天 XA 几乎只在金融核心系统出现,互联网业务一律走最终一致。Seata 的 AT 模式虽然在概念上借鉴了 2PC,但用本地事务 + undo_log 实现了无锁的「软 2PC」,是 XA 的现代替代。

四、方案三:TCC(Try-Confirm-Cancel)

4.1 核心思想

每个需要实现事务的业务需要三个接口

接口作用
tryXXX业务检查预留资源(如冻结额度、占库存)
confirmXXX执行业务,使用资源
cancelXXX回滚业务,释放资源

三个接口由协调器统一管理调用。TCC 借鉴 XA 的统一资源管理,但是不是两阶段提交

关键差异不同资源之间没有锁,事务过程中数据没有锁,没有隔离。TCC 把「隔离性」交给业务自己实现(通过 try 阶段的资源冻结)。

4.2 幂等性约束

由于分布式系统的架构设计,无法保证调用各个业务的 commit/cancel 方法的有序性与次数,所以 commit/cancel 方法需要满足幂等性

这是 TCC 落地最容易被忽略、也最容易踩坑的一点——网络重试、协调器重启都会导致 confirm/cancel 被调用多次。

4.3 协调器职责

协调器接管事务管理,类似于 JTA 的独立事务管理器:

4.4 实现一:REST 接口方式

@RestController
public class UserResource {

    @Transactional
    public void tryCharge(DTO dto) { /* TODO */ }

    @Transactional
    public void confirmCharge(DTO dto) { /* TODO */ }

    @Transactional
    public void cancelCharge(DTO dto) { /* TODO */ }
}

TCC UserResource 代码:tryCharge / confirmCharge / cancelCharge 三个方法

每个服务暴露三个 REST 端点,协调器通过 HTTP 调用。

4.5 时序示例

TCC 下单时序:Order 调用 User 的 tryCharge,事务完成后协调器异步调 confirm/cancel

步骤动作
1下单(外部触发 Order 服务)
2-3Order 开启事务,向协调器注册新事务
4Order 调用 User 的 tryCharge()(Try 阶段)
5User 返回资源路径,Order 向协调器注册分支事务
6Order 处理本地订单业务
8Order 通知协调器事务完成
9协调器异步调用 User 的 confirm/cancel

注意步骤 9 是异步——主流程(步骤 1-8)已经返回给用户了,confirm/cancel 由协调器后台触发。这是 TCC 性能优于 XA 的关键。

4.6 实现二:Service 规范

public interface TccMethod<P extends TccRequest<R>, R extends Serializable> {

    R doTry(P param);   // Try 阶段执行业务逻辑

    void doConfirm(P param);  // Confirm 阶段确认提交

    void doCancel(P param);   // Cancel 阶段回滚
}

TccMethod 接口定义:泛型约束 + 三阶段方法

通过泛型约束:

业务实现这个接口后,由 TCC 框架(如 HmilyByteTCCEasyTransaction)接管事务协调。

五、方案四:本地消息表

5.1 思路

采用 BASE 原理保证事务的最终一致性

这是工程上最常用的方案——把「分布式事务」问题降维成「本地事务 + 定时任务」问题。

核心做法

  1. 业务库中新增一张「本地消息表」,与业务表在同一个数据库
  2. 业务操作和写消息表放在一个本地事务——要么都成功,要么都失败
  3. 一个定时任务扫描消息表,把未发送的消息投递给 MQ
  4. 投递成功后,更新消息表状态为「已发送」
  5. 下游服务消费消息,处理完成后回 ACK
┌──── 本地数据库(一个事务) ────┐
│  1. update business_table     │
│  2. insert local_message_table│
└────────────────────────────────┘

                ▼ (定时任务扫描)
        投递到 MQ ──► 下游消费

                ▼ (ACK)
        更新 message 状态 = SENT

5.2 优势

优势说明
无需引入协调器业务库 + MQ + 定时任务即可,依赖少
本地事务保证业务和消息表在同一事务,强一致
下游解耦通过 MQ 解耦,下游失败可重试
工程落地简单比起 TCC,业务侵入性低

5.3 缺点

现代视角(2026)补一句:本地消息表思想在 2024 年后演化成了 「事务消息」(如 RocketMQ 的事务消息、阿里 Seata 的 Saga/AT 模式)——把「消息表」内置到 MQ 服务端,业务感知不到。但本质还是同一套思路。

六、方案五:Saga 模式(长事务)

这套原文笔记没写,但 2017 年以后 Saga 在微服务圈已经成了长事务的事实标准——补一段。

6.1 思路

Saga 把一个长事务拆成 N 个本地事务 T1, T2, ..., Tn,每个本地事务都有对应的补偿事务 C1, C2, ..., Cn

正向:T1 ─► T2 ─► T3 ─► T4  ✅ 完成

                  ✗ 失败

补偿:     C2 ◄─ C1            🔄 回滚

6.2 两种实现

模式流程适用
编排式(Choreography)各服务监听事件,没有中心协调器服务数少,链路简单
协调式(Orchestration)有一个 Saga 协调器统一调度服务数多,复杂流程

6.3 vs TCC

维度TCCSaga
阶段Try / Confirm / CancelTn(业务) / Cn(补偿)
隔离性Try 阶段预留资源无隔离——中间状态可见
业务侵入高(要写 3 个接口)中(多写一个补偿接口)
适用短事务、需要隔离长事务、多步骤

选择经验:业务流程 ≤ 3 步、且对中间状态可见性敏感 → TCC;业务流程长、能容忍中间状态 → Saga。

七、工业实现:Seata 三种模式(2026 视角补充)

阿里 2019 年开源的 Seata(Simple Extensible Autonomous Transaction Architecture)是国内分布式事务的事实标准框架。它把上述思想工程化为三种内置模式:

模式对应方案特点
AT 模式无锁 2PC(变种)默认推荐——业务零侵入,自动生成 undo_log 反向 SQL
TCC 模式TCC业务自定义 try/confirm/cancel
Saga 模式Saga状态机驱动,适合长流程
XA 模式XA 2PC1.2+ 引入,需 DB 支持 XA

AT 模式的精妙:业务只写普通的 @Transactional,Seata 拦截 SQL,自动生成 undo_log。提交前所有分支事务都是本地事务,全局提交时异步清理 undo_log,全局回滚时用 undo_log 反向恢复。

代价:AT 模式靠全局锁保证写隔离,性能不如 TCC,但业务零侵入是它最大的杀手锏——这也是为什么 2026 年 Seata AT 模式仍是国内中小厂首选。

八、五种方案对比总结

方案一致性性能复杂度业务侵入适用场景
XA / 2PC强一致❌ 差金融核心、传统行业
TCC最终一致(隔离好)✅ 好(3 接口)高并发短事务,资金类
消息驱动最终一致✅ 好业务流程清晰的链路
本地消息表最终一致✅ 好工程首选、中小业务
Saga最终一致(无隔离)✅ 好中(补偿接口)长事务、多步骤

8.1 选型口诀

强一致选 XA,高并发选 TCC,工程首选本地消息表,长事务选 Saga,已有 Seata 直接上 AT。

8.2 通用要求

无论哪种方案,这三条是共识

要求说明
业务幂等MQ 重试、协调器重试都会发生,confirm/cancel/消费都必须幂等
可观测分布式事务调试极难,必须能查每一步状态
兜底预案任何方案都有覆盖不到的异常,必须有人工补偿通道

九、与之前笔记的串联

本篇只讲事务机制,业务侧的拆分逻辑见 DDD 系列——限界上下文(Bounded Context) 怎么定,决定了你有多少个微服务、有多少跨服务事务需要做。可以对照看:

事务的本地基础MySQL 隔离级别与 Spring 事务——理解不了本地事务的隔离级别,分布式事务的取舍就是空中楼阁。

消息的幂等性讨论见 Kafka(下):消费者、消息有序性、命令与集群——消息驱动方案落地必读。

十、小结

分布式事务没有银弹。所有的方案,本质都是在「一致性 / 可用性 / 性能 / 业务侵入」四个维度上做权衡

时代主流方案
2010 年前XA / 2PC(强一致,金融行业)
2010-2018TCC / 本地消息表(互联网大厂)
2019-2024Seata AT/TCC/Saga(一站式框架)
2025+Service Mesh + Dapr(把事务能力下沉到基础设施层)

核心知识到 2026 年没变——CAP/BASE、幂等、补偿、两阶段,这套底层原理再放 10 年依然适用。变的只是「谁来管事务」:业务代码 → 中间件 → 基础设施。

现代视角补一句(2026):今天的趋势是让业务忘记分布式事务的存在——Seata AT 已经做到了「写 @GlobalTransactional 就完事」,Service Mesh 把事务能力下沉到 Sidecar。业务代码层越来越干净,事务复杂度全部下沉到平台——这是分布式事务演化的终极方向。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.CAP 与 BASE:分布式系统的理论基石
  2. 02.拜占庭将军问题:两忠一叛、口信与签名消息之解
  3. 03.ZooKeeper 原理与实践:数据模型、Watcher 与 ZAB 协议
  4. 04.分布式事务五种方案:消息驱动 / XA / TCC / 本地消息表 / Saga
  5. 05.微服务限流与容错:雪崩效应、熔断器与三种限流算法

上一篇
DDD 复杂业务系统设计(一):为什么从“领域”开始,比从“数据库”开始更靠谱
下一篇
CAP 与 BASE:分布式系统的理论基石