欢迎光临
我们一直在努力

孤舟笔记 分布式与微服务篇十四 分布式事务有哪些解决方案?2PC、TCC、Saga一次讲透

文章目录

    • 先说结论
    • 2PC:两阶段提交
    • TCC:Try-Confirm-Cancel
    • Saga:正反操作链
    • 本地消息表:最实用
    • 回答技巧与点评
      • 加分回答
      • 面试官点评

个人网站

微服务架构下,一个业务操作跨多个服务,怎么保证数据一致?2PC、TCC、Saga、本地消息表……方案这么多,到底啥区别?面试官问这题,他想听的是:你能不能对比各方案的一致性模型、性能、侵入性,并给出场景化选型建议?

先说结论

方案一致性性能侵入性典型场景
2PC 强一致 数据库层面分布式事务
TCC 最终一致 资金交易
Saga 最终一致 长流程业务编排
本地消息表 最终一致 异步场景
事务消息 最终一致 MQ 生态

|一句话记住:2PC像"全体举手表态"——慢但稳;TCC像"先冻结再确认"——麻烦但精确;Saga像"做错就道歉"——快但得写补偿"

2PC:两阶段提交

第一阶段(Prepare):协调者问所有参与者"能提交吗?"

第二阶段(Commit/Rollback):全部能提交就提交,有一个不能就全部回滚。

协调者 参与者A 参与者B
│ │ │
│── Prepare ──→│ │
│ │── Yes ──→ │
│── Prepare ───────────────→│
│ │ │── Yes ──→
│ │ │
│── Commit ──→│ │ 👈 全部同意才提交
│── Commit ───────────────→│

优点缺点
强一致 同步阻塞(参与者锁住资源等协调者)
实现简单 协调者单点故障
侵入低 网络分区可能数据不一致

2PC 适合数据库层面的分布式事务(如 MySQL XA),不适合微服务(阻塞太严重)。

TCC:Try-Confirm-Cancel

每个服务提供三个接口:

// 扣款服务
public class AccountTccService {

// Try:冻结资源(不是真扣!)
public boolean tryDeduct(String accountId, BigDecimal amount) {
// 冻结金额
accountMapper.freeze(accountId, amount); // 👈 冻结,不是扣
return true;
}

// Confirm:确认扣款
public boolean confirmDeduct(String accountId, BigDecimal amount) {
accountMapper.deduct(accountId, amount); // 👈 真扣
return true;
}

// Cancel:取消冻结
public boolean cancelDeduct(String accountId, BigDecimal amount) {
accountMapper.unfreeze(accountId, amount); // 👈 解冻
return true;
}
}

TCC 流程

1. Try阶段:所有服务冻结资源
├── 订单服务 → 创建订单(待确认)
├── 库存服务 → 冻结库存
└── 支付服务 → 冻结金额 👈

2. 全部Try成功 → Confirm
├── 订单服务 → 确认订单
├── 库存服务 → 扣减库存
└── 支付服务 → 扣减金额

3. 任一Try失败 → Cancel
├── 订单服务 → 取消订单
├── 库存服务 → 释放冻结
└── 支付服务 → 解冻金额

优点:性能好(不阻塞)、一致性强。缺点:侵入性极高(每个服务写三个接口)、补偿逻辑复杂。

Saga:正反操作链

Saga 把长事务拆成多个本地事务,每个本地事务配一个补偿操作:

正向链路:创建订单 → 扣库存 → 扣款 → 发通知
补偿链路:取消订单 → 恢复库存 → 退款 → 撤通知 👈

如果"扣款"失败 → 补偿"扣库存" → 补偿"创建订单"

// Saga 编排(简化)
SagaDefinition saga = SagaDefinition.builder()
.step("createOrder", orderService::create, orderService::cancel) 👈 正向+补偿
.step("deductStock", stockService::deduct, stockService::restore)
.step("deductPayment", payService::deduct, payService::refund)
.build();

saga.execute(); // 任何步骤失败,自动反向补偿

优点:不阻塞、长流程友好。缺点:只能保证最终一致(中间状态可见)、补偿逻辑难写。

本地消息表:最实用

把分布式事务拆成本地事务 + 异步消息:

// 订单服务:创建订单 + 写消息表(同一个本地事务!)
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
messageMapper.insert(new Message("deduct_stock", orderJson)); // 👈 同一事务
}

// 定时任务:扫描消息表,发送MQ
@Scheduled
public void sendMessages() {
List<Message> messages = messageMapper.findPending();
for (Message msg : messages) {
mqProducer.send(msg);
messageMapper.updateStatus(msg.getId(), "SENT"); // 👈 发送成功标记
}
}

// 库存服务:消费MQ消息
@RocketMQMessageListener
public void onMessage(String orderJson) {
stockService.deductStock(parseOrder(orderJson));
// 消费失败 → MQ重试 → 最终一致 👈
}

优点:实现简单、不依赖 TCC 框架、最终一致。缺点:消息表需要定时清理、消息可能重复(消费端需幂等)。

分布式事务方案全景

2PC(强一致)
├── 两阶段:Prepare → Commit/Rollback
├── 优点:强一致,侵入低
└── 缺点:阻塞,协调者单点

TCC(最终一致)
├── 三接口:Try → Confirm/Cancel
├── 优点:不阻塞,一致性强
└── 缺点:侵入高,三倍代码

Saga(最终一致)
├── 正反链:正向操作 + 补偿操作
├── 优点:长流程友好
└── 缺点:中间状态可见,补偿复杂

消息表(最终一致)
├── 本地事务 + 异步消息
├── 优点:简单实用
└── 缺点:需幂等,需清理

口诀:2PC两阶段强一致,阻塞卡住性能低;
TCC三步冻结法,代码量大但精确;
Saga正反补偿链,长流程用最合适;
消息表最实用,本地事务加异步

回答技巧与点评

标准回答:分布式事务有五种方案:1)2PC——两阶段提交,强一致但阻塞严重,适合数据库层面;2)TCC——Try 冻结/Confirm 确认/Cancel 取消,性能好但侵入高,适合资金场景;3)Saga——正向操作+补偿链,适合长流程;4)本地消息表——本地事务+异步消息,简单实用;5)事务消息——MQ 原生支持。趋势是最终一致优于强一致,推荐本地消息表或事务消息。

加分回答

  • Seata 框架:阿里开源的分布式事务框架,支持 AT(自动补偿,类似 2PC 增强)、TCC、Saga 四种模式。AT 模式最常用——自动解析 SQL 生成回滚日志,业务零侵入
  • 事务消息的原理:RocketMQ 事务消息分两步:先发半消息(消费者不可见)→ 执行本地事务 → 根据结果提交或回滚。比本地消息表少了一张消息表,但依赖特定 MQ
  • 避免分布式事务的设计:最好的分布式事务是不需要分布式事务。通过合并服务、事件驱动架构、CQRS 等方式,从架构层面避免跨服务事务
  • 面试官点评

    这道题考的是你对 分布式一致性方案 的全面认识。最忌讳的回答是只说 TCC 或只说 2PC——面试官想听的是 方案对比和场景选型。能讲清每种方案的一致性模型和代价,再给出"大多数场景用消息表"这种务实建议,就是高分回答。

    原文阅读


    内容有帮助?点赞、收藏、关注三连!评论区等你 💪

    赞(0)
    未经允许不得转载:171主机测评 » 孤舟笔记 分布式与微服务篇十四 分布式事务有哪些解决方案?2PC、TCC、Saga一次讲透
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址