微服务架构下的分布式事务实践:从 2PC 到 Saga 的演进选型
微服务架构下的分布式事务实践:从 2PC 到 Saga 的演进选型
一、从单体到微服务:事务边界被打破
在单体架构中,一个业务操作往往对应一次本地数据库事务,ACID 特性由数据库引擎直接保证。但当系统拆分为微服务后,一次"下单"操作可能跨越订单、库存、账户三个独立数据库,原本一条 BEGIN ... COMMIT 就能搞定的事,变成了跨进程、跨网络的协作问题。
分布式事务的核心矛盾在于:强一致性与可用性不可兼得。跨服务调用随时可能因网络抖动、节点宕机而失败,若坚持同步阻塞式提交,系统吞吐将被严重拖累。
二、四种主流方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低 | 中 | 同源异构数据库 |
| TCC | 强一致 | 中 | 高 | 资金类核心业务 |
| Saga | 最终一致 | 高 | 中 | 长流程业务编排 |
| 本地消息表 | 最终一致 | 高 | 低 | 异步解耦场景 |
两阶段提交(2PC)通过协调者锁定所有参与者资源再统一提交,能保证强一致,但同步阻塞、协调者单点故障会导致整个事务长时间挂起,在微服务高并发场景下几乎不可用。
TCC(Try-Confirm-Cancel)把每个操作拆成三个阶段,业务侵入性强,需要为每个接口实现确认与补偿逻辑,开发成本高,但对资金扣减等必须强一致的场景仍是首选。
三、Saga 模式实战
Saga 将长事务拆分为一串本地事务 T1, T2, ..., Tn,每个 Ti 配套一个补偿操作 Ci。任一 Ti 失败时,按序执行 Ci-1, ..., C1 回滚。相比 TCC,Saga 只需正向与补偿两套逻辑,复杂度更低。
以"下单 → 扣库存 → 扣余额"为例,基于编排式(Orchestration)Saga 的核心实现:
@Service
public class OrderSagaOrchestrator {
@Autowired private OrderService orderService;
@Autowired private InventoryClient inventoryClient;
@Autowired private AccountClient accountClient;
@Autowired private SagaLogRepository sagaLog;
public void execute(CreateOrderCmd cmd) {
Long sagaId = sagaLog.start(cmd);
try {
// T1: 创建订单(PENDING 状态)
Long orderId = orderService.create(cmd);
sagaLog.forward(sagaId, "createOrder", orderId);
// T2: 扣减库存
inventoryClient.deduct(cmd.getSkuId(), cmd.getQty());
sagaLog.forward(sagaId, "deductInventory", cmd.getQty());
// T3: 扣减余额
accountClient.charge(cmd.getUserId(), cmd.getAmount());
sagaLog.forward(sagaId, "chargeAccount", cmd.getAmount());
orderService.markPaid(orderId);
sagaLog.complete(sagaId);
} catch (Exception e) {
compensate(sagaId, e);
}
}
// 逆序执行已完成步骤的补偿
private void compensate(Long sagaId, Exception e) {
SagaState state = sagaLog.getState(sagaId);
if (state.done("chargeAccount")) accountClient.refund(cmd);
if (state.done("deductInventory")) inventoryClient.restore(cmd);
if (state.done("createOrder")) orderService.cancel(state.orderId);
sagaLog.abort(sagaId, e.getMessage());
}
}
关键设计要点:
- Saga 日志持久化:每步执行前先写日志,保证宕机重启后能按日志恢复并继续补偿,这是实现幂等与可恢复的基础。
- 补偿操作必须幂等:网络重试可能导致同一补偿被执行多次,
refund、restore等接口需通过业务流水号去重。 - 补偿不可失败:补偿本身若失败,只能重试加告警,不能无限嵌套回滚,必要时转人工介入。
四、选型建议
- 资金、库存等强一致核心域:优先 TCC,宁可牺牲吞吐也要保证不超卖、不丢钱。
- 订单履约、跨服务流程编排:选 Saga,最终一致加补偿,吞吐与一致性兼顾。
- 简单异步通知、日志埋点:本地消息表加 MQ 重试即可,无需引入重型事务框架。
五、总结
分布式事务没有银弹。从 2PC 的强一致阻塞,到 Saga 的最终一致补偿,本质是在一致性强度与系统吞吐之间做权衡。落地时先问自己:这个业务真的需要强一致吗?大多数场景下,可观测的最终一致加完善的补偿与告警机制,比脆弱的强一致更值得信赖。
- 点赞
- 收藏
- 关注作者
评论(0)