文章目录
-
- 先说结论
- 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 原生支持。趋势是最终一致优于强一致,推荐本地消息表或事务消息。
加分回答
面试官点评
这道题考的是你对 分布式一致性方案 的全面认识。最忌讳的回答是只说 TCC 或只说 2PC——面试官想听的是 方案对比和场景选型。能讲清每种方案的一致性模型和代价,再给出"大多数场景用消息表"这种务实建议,就是高分回答。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪