跳到主要内容

分布式事务

问题

什么是分布式事务?2PC、TCC、Saga 各自是什么原理?如何在实际项目中选择合适的分布式事务方案?

答案

为什么需要分布式事务

在微服务或分库分表架构中,一个业务操作可能涉及多个数据库或多个服务,本地事务无法保证跨库/跨服务的数据一致性。

核心问题:订单创建成功,但库存扣减失败,数据不一致。

分布式事务方案概览

方案一致性性能复杂度适用场景
2PC强一致性单应用跨库
TCC最终一致性较高金融/电商核心链路
Saga最终一致性长事务、跨服务
本地消息表最终一致性异步解耦
事务消息最终一致性RocketMQ 生态

2PC(两阶段提交)

2PC(Two-Phase Commit)是最经典的分布式事务协议,由一个 协调者(Coordinator) 和多个 参与者(Participant) 组成。

如果有参与者 Prepare 失败

2PC 的问题

问题说明
同步阻塞Prepare 后参与者锁定资源,等待协调者决策
单点故障协调者宕机,参与者锁定资源无法释放
数据不一致Commit 发送给部分参与者后协调者宕机
并发低整个过程锁定资源

MySQL 中的 2PC(XA 事务)

-- XA 事务(跨库事务)
XA START 'txn1';
INSERT INTO db1.orders VALUES (...);
XA END 'txn1';
XA PREPARE 'txn1';

-- 另一个连接
XA START 'txn2';
UPDATE db2.stock SET quantity = quantity - 1;
XA END 'txn2';
XA PREPARE 'txn2';

-- 两个都 Prepare 成功后
XA COMMIT 'txn1';
XA COMMIT 'txn2';

TCC(Try-Confirm-Cancel)

TCC 将业务逻辑分为三个阶段:

以转账为例

阶段A 账户(转出方)B 账户(转入方)
Try检查余额 ≥ 100,冻结 100无操作(或预增)
Confirm扣减冻结金额 100增加 100
Cancel解冻 100回滚预增
// TCC 接口设计
public interface AccountTccService {
// Try:冻结金额
@TwoPhaseBusinessAction(name = "deduct",
commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeduct(BusinessActionContext ctx,
String accountId, BigDecimal amount);

// Confirm:扣减冻结金额
boolean confirm(BusinessActionContext ctx);

// Cancel:解冻金额
boolean cancel(BusinessActionContext ctx);
}

TCC 需要解决的问题

问题解决方案
空回滚Cancel 被调用但 Try 未执行 → Cancel 需判断 Try 是否执行过
幂等Confirm/Cancel 可能被重复调用 → 需要幂等设计
悬挂Cancel 先于 Try 到达 → Try 需判断是否已 Cancel

Saga 模式

Saga 将长事务拆分为一系列 本地短事务,每个本地事务有对应的 补偿操作

两种编排方式

方式说明优势劣势
编排式(Orchestration)中央协调器控制流程流程清晰、易于管理协调器是单点
协同式(Choreography)各服务通过事件驱动无中心、松耦合流程分散、难追踪

本地消息表

利用数据库的本地事务,将业务操作和消息写入放在同一个事务中。

优势:不依赖分布式事务框架,实现简单
劣势:需要维护消息表,定时扫描有延迟

事务消息(RocketMQ)

RocketMQ 原生支持事务消息,原理类似本地消息表但更优雅。

方案对比与选型

维度2PCTCCSaga本地消息表事务消息
一致性强一致最终一致最终一致最终一致最终一致
性能较高
业务侵入高(3个接口)中(补偿接口)
隔离性弱(需业务保证)
复杂度
框架XASeata TCCSeata Saga自行实现RocketMQ

Seata 分布式事务框架

Seata 是阿里开源的分布式事务框架,支持 AT、TCC、Saga、XA 四种模式。

AT 模式(最常用,自动补偿):

  • 一阶段:解析 SQL,自动记录数据快照(before image),执行业务 SQL
  • 二阶段-提交:删除快照(异步,很快)
  • 二阶段-回滚:用快照恢复原数据
优势:对业务无侵入,像本地事务一样使用
劣势:需要全局锁,性能有一定损耗

常见面试问题

Q1: 2PC 有什么问题?

答案

  1. 同步阻塞:所有参与者在 Prepare 后锁定资源等待,降低并发
  2. 单点故障:协调者宕机后参与者资源无法释放
  3. 数据不一致:协调者发送 Commit 给部分参与者后宕机,已收到的提交了,未收到的还在等
  4. 性能差:两轮网络通信 + 资源锁定时间长

Q2: TCC 如何处理空回滚和幂等?

答案

空回滚:Cancel 被调用但 Try 还未执行(如 Try 超时,协调者先发了 Cancel)。

  • 解决:在 Cancel 中检查业务记录,如果 Try 没执行过,直接返回成功

幂等:网络重试导致 Confirm/Cancel 被重复调用。

  • 解决:记录事务执行状态(幂等表),重复调用直接返回

悬挂:Cancel 先到达并执行完毕,之后 Try 才到达。

  • 解决:Try 执行前检查是否已经 Cancel,如果已 Cancel 则不执行

Q3: Saga 和 TCC 的区别?

答案

对比TCCSaga
资源锁定Try 阶段预留不预留,直接执行
隔离性有(冻结资源期间其他事务看不到)无(直接修改,中间状态可见)
补偿方式Cancel 回滚预留反向操作补偿
实时性资源锁定期间不可用资源始终可用
适用场景金融(需要隔离性)长流程(需要高可用)

Q4: 实际项目中如何选择分布式事务方案?

答案

  1. 能用本地事务就不用分布式事务:通过合理的服务设计减少跨服务事务
  2. 数据一致性要求高(金融):TCC 或 2PC
  3. 长流程编排:Saga
  4. 异步解耦:本地消息表或事务消息(RocketMQ)
  5. Java 生态:Seata AT 模式(对业务无侵入)

实际中,大部分互联网业务用 最终一致性 就够了(本地消息表 + 补偿),只有金融核心链路才需要 TCC。

Q5: 什么是 BASE 理论?和 ACID 什么关系?

答案

BASE 是分布式系统对 ACID 的妥协:

  • BAsically Available:基本可用(允许部分功能降级)
  • Soft state:软状态(允许存在中间状态)
  • Eventually consistent:最终一致性(不要求实时一致)

关系:

  • ACID → 单数据库的强一致性保证
  • BASE → 分布式系统在 CAP 限制下的实用性妥协
  • 分布式事务(TCC/Saga/消息)本质上是在实现 BASE 的最终一致性

更多 CAP/BASE 理论详见 Java 系列 CAP 与 BASE。

相关链接