分布式事务的挑战

当单体应用拆分为微服务后,一个业务操作可能涉及多个服务和多个数据库。传统的本地事务(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模式的核心原理:

  1. 拦截业务SQL,解析表结构和条件,生成before image(前置镜像)和after image(后置镜像)
  2. 将before image、after image和业务SQL在同一个本地事务中写入UNDO_LOG表
  3. 提交阶段:各分支事务本地提交
  4. 回滚阶段:根据UNDO_LOG自动生成反向SQL(如INSERT→DELETE, UPDATE→回滚到before image, DELETE→重新INSERT)

生产实践建议

  • 优先使用最终一致性:除非业务必须,不要追求强一致,性能和可用性更重要
  • 业务设计上尽量避免分布式事务:通过合理的服务拆分和边界设计,使大部分操作在一个服务内完成
  • 幂等性设计:所有分布式事务相关的接口必须支持幂等,防止重复执行导致数据错误
  • 监控告警:对悬挂事务、悬挂补偿等异常状态设置自动告警
  • 选择合适的方案:短事务、数据库同构选2PC;跨服务强业务语义选TCC;异步场景选消息队列;低侵入需求选Seata AT

总结

分布式事务没有银弹,每种方案都有其适用场景和取舍。在实际项目中,最优先的策略是通过合理的设计来避免分布式事务;当无法避免时,根据业务特点选择最适合的方案。Seata AT以其零侵入、自动补偿的特性成为大多数场景的优选方案,而TCC虽然侵入性强,但在性能和一致性平衡上更具灵活性。

理解底层原理而非仅仅使用框架,才能在面对复杂分布式问题时做出正确的技术决策。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部