分布式事务的挑战
当单体应用拆分为微服务后,一个业务操作可能涉及多个服务和多个数据库。传统的本地事务(ACID)无法跨越服务边界,分布式事务成为必须面对的核心难题。BASE理论告诉我们,在分布式系统中,我们需要在一致性(Consistency)和可用性(Availability)之间做出权衡。
常见解决方案全景对比
主要的分布式事务方案包括:2PC/XA(强一致但性能低)、TCC(最终一致,适用于跨服务的业务补偿)、Saga(最终一致,适合长事务和业务流程编排)、消息队列+最终一致(高一致性容忍异步延迟的场景)以及Seata AT(最终一致且侵入性低,支持自动补偿)。
两阶段提交(2PC/XA)
2PC是最经典的分布式事务协议,分为投票阶段和提交阶段。由事务协调者(Transaction Coordinator)统一管理所有参与者(Resource Manager)。
阶段1 - Prepare:
Coordinator → 所有参与者: "准备提交"
参与者执行事务但不提交,记录undo/redo日志
参与者回复: Yes / No
阶段2 - Commit/Rollback:
如果全部Yes → Commit
如有任何No → Rollback
缺陷:同步阻塞(所有参与者锁定资源等待协调者决策)、单点故障(协调者宕机导致参与者无限等待)、数据不一致(第二阶段部分参与者收不到指令)。
TCC模式(Try-Confirm-Cancel)
TCC是一种业务层面的分布式事务方案,将每个业务操作拆分为三个阶段:
- Try:资源预留与冻结(如预扣库存、预授权金额)
- Confirm:确认执行,真正扣减资源(幂等操作)
- Cancel:回滚释放资源(幂等操作,支持空回滚)
@LocalTCC
public interface AccountService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
// 实现
@Service
public class AccountServiceImpl implements AccountService {
@Override
public boolean tryDeduct(String userId, BigDecimal amount) {
// 检查余额是否充足,冻结对应金额
BigDecimal balance = accountMapper.getBalance(userId);
if (balance.compareTo(amount) < 0 xss=removed xss=removed xss=removed xss=removed>
注意事项:必须保证Confirm和Cancel的幂等性;需要处理空回滚(Try未执行但Cancel被调用);需要处理悬挂(Cancel先于Try执行,需记录Cancel状态阻止后续Try)。
Saga模式
Saga将一个长事务拆分为一系列本地事务,每个本地事务有对应的补偿事务。执行失败时,按逆序执行补偿操作。分为编排(Choreography)和协调(Orchestration)两种实现方式。
编排模式:
Service A --事件--> Service B --事件--> Service C
失败:Service C --补偿事件--> Service B --补偿事件--> Service A
协调模式:
Saga Orchestrator
→ 调用Service A
→ 调用Service B
→ 调用Service C(失败)
→ 调用Service B的补偿
→ 调用Service A的补偿
Seata AT模式(推荐)
Seata AT(Automatic Transaction)模式是阿里开源的分布式事务框架,最大的优势是对业务零侵入。通过拦截SQL解析UNDO_LOG,自动生成回滚日志。
// 使用示例 - 只需一个注解
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 本地事务:创建订单
orderService.create(order);
// 2. 远程调用:扣减库存
storageService.deduct(order.getProductId(), order.getCount());
// 3. 远程调用:扣减余额
accountService.debit(order.getUserId(), order.getMoney());
}
Seata AT模式的核心原理:
- 拦截业务SQL,解析表结构和条件,生成before image(前置镜像)和after image(后置镜像)
- 将before image、after image和业务SQL在同一个本地事务中写入UNDO_LOG表
- 提交阶段:各分支事务本地提交
- 回滚阶段:根据UNDO_LOG自动生成反向SQL(如INSERT→DELETE, UPDATE→回滚到before image, DELETE→重新INSERT)
生产实践建议
- 优先使用最终一致性:除非业务必须,不要追求强一致,性能和可用性更重要
- 业务设计上尽量避免分布式事务:通过合理的服务拆分和边界设计,使大部分操作在一个服务内完成
- 幂等性设计:所有分布式事务相关的接口必须支持幂等,防止重复执行导致数据错误
- 监控告警:对悬挂事务、悬挂补偿等异常状态设置自动告警
- 选择合适的方案:短事务、数据库同构选2PC;跨服务强业务语义选TCC;异步场景选消息队列;低侵入需求选Seata AT
总结
分布式事务没有银弹,每种方案都有其适用场景和取舍。在实际项目中,最优先的策略是通过合理的设计来避免分布式事务;当无法避免时,根据业务特点选择最适合的方案。Seata AT以其零侵入、自动补偿的特性成为大多数场景的优选方案,而TCC虽然侵入性强,但在性能和一致性平衡上更具灵活性。
理解底层原理而非仅仅使用框架,才能在面对复杂分布式问题时做出正确的技术决策。

发表评论 取消回复