问题

分布式事务的解决方案有哪些?

答案

1. 核心概念

分布式事务解决方案可以分为两大类:

  • 刚性事务:追求强一致性,遵循ACID原则(2PC、3PC、XA)
  • 柔性事务:追求最终一致性,遵循BASE理论(TCC、Saga、消息方案)

2. 刚性事务方案

方案1:2PC(两阶段提交)

原理

协调者(TC)              参与者1              参与者2
   |                     |                    |
   |---准备(Prepare)---->|                    |
   |---准备(Prepare)-------------------->     |
   |                     |                    |
   |<---准备成功---------|                    |
   |<---准备成功-----------------------------|
   |                     |                    |
   |---提交(Commit)----->|                    |
   |---提交(Commit)--------------------->     |
   |                     |                    |
   |<---提交成功---------|                    |
   |<---提交成功-----------------------------|

阶段说明

  • 阶段1(准备阶段):协调者询问所有参与者是否可以执行事务,参与者执行事务但不提交
  • 阶段2(提交阶段):如果所有参与者都准备成功,协调者发送提交命令;否则发送回滚命令

实现示例

// 协调者
public class TwoPhaseCommitCoordinator {
    private List<Participant> participants;
    
    public boolean executeTransaction() {
        // 阶段1:准备阶段
        List<Boolean> prepareResults = new ArrayList<>();
        for (Participant participant : participants) {
            boolean result = participant.prepare();
            prepareResults.add(result);
        }
        
        // 阶段2:提交或回滚
        boolean allSuccess = prepareResults.stream().allMatch(r -> r);
        if (allSuccess) {
            // 所有参与者准备成功,提交
            for (Participant participant : participants) {
                participant.commit();
            }
            return true;
        } else {
            // 有参与者准备失败,回滚
            for (Participant participant : participants) {
                participant.rollback();
            }
            return false;
        }
    }
}

优点

  • 强一致性保证
  • 实现相对简单

缺点

  • 同步阻塞:参与者在准备阶段会锁定资源
  • 单点故障:协调者故障导致参与者阻塞
  • 数据不一致:网络分区可能导致部分提交部分回滚

适用场景:对一致性要求极高、并发量不大的场景(如银行转账)

方案2:3PC(三阶段提交)

原理:在2PC基础上增加超时机制和CanCommit阶段。

阶段1:CanCommit(询问)
  协调者询问参与者是否可以执行事务(不执行)
  
阶段2:PreCommit(预提交)
  参与者执行事务但不提交
  
阶段3:DoCommit(提交)
  参与者提交事务

改进点

  • 引入超时机制:参与者超时后自动提交
  • CanCommit阶段:减少阻塞时间

优点

  • 降低阻塞时间
  • 降低单点故障影响

缺点

  • 复杂度更高
  • 仍无法完全解决数据不一致问题
  • 性能较差

适用场景:实际生产环境很少使用,理论意义大于实际意义

方案3:XA协议

原理:基于2PC的分布式事务规范,由数据库厂商实现。

// 使用JTA实现XA事务
@Transactional
public void xaTransaction() throws Exception {
    // 创建事务管理器
    UserTransaction userTransaction = 
        (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
    
    try {
        userTransaction.begin();
        
        // 操作数据库1
        DataSource ds1 = getDataSource1();
        Connection conn1 = ds1.getConnection();
        // ... 执行SQL
        
        // 操作数据库2
        DataSource ds2 = getDataSource2();
        Connection conn2 = ds2.getConnection();
        // ... 执行SQL
        
        userTransaction.commit();
    } catch (Exception e) {
        userTransaction.rollback();
        throw e;
    }
}

优点

  • 标准化,数据库原生支持
  • 强一致性

缺点

  • 性能差:锁定资源时间长
  • 只支持关系型数据库
  • 对业务侵入性强

3. 柔性事务方案

方案4:TCC(Try-Confirm-Cancel)

原理:业务层面的两阶段提交。

阶段1:Try(尝试)
  - 完成业务检查
  - 预留必要的业务资源
  
阶段2:Confirm/Cancel
  - Confirm: 确认执行业务操作
  - Cancel: 释放预留的业务资源

实现示例

// 账户服务TCC实现
public class AccountServiceTCC {
    
    // Try阶段:冻结金额
    @Transactional
    public boolean tryDeduct(String accountId, BigDecimal amount) {
        Account account = accountDao.getById(accountId);
        if (account.getBalance().compareTo(amount) < 0) {
            return false; // 余额不足
        }
        // 冻结金额
        account.setBalance(account.getBalance().subtract(amount));
        account.setFrozenAmount(account.getFrozenAmount().add(amount));
        accountDao.update(account);
        return true;
    }
    
    // Confirm阶段:扣减冻结金额
    @Transactional
    public void confirmDeduct(String accountId, BigDecimal amount) {
        Account account = accountDao.getById(accountId);
        account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
        accountDao.update(account);
    }
    
    // Cancel阶段:解冻金额
    @Transactional
    public void cancelDeduct(String accountId, BigDecimal amount) {
        Account account = accountDao.getById(accountId);
        account.setBalance(account.getBalance().add(amount));
        account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
        accountDao.update(account);
    }
}

优点

  • 性能较好:不长时间锁定资源
  • 数据一致性好

缺点

  • 实现复杂:需要实现Try、Confirm、Cancel三个方法
  • 对业务侵入性强
  • 需要考虑幂等性

适用场景:对一致性和性能都有要求的场景(如电商下单)

方案5:Saga模式

原理:长事务拆分为多个本地短事务,每个本地事务都有对应的补偿操作。

正向流程:T1 -> T2 -> T3 -> T4
补偿流程:C4 -> C3 -> C2 -> C1

实现方式

方式1:协同式Saga(事件驱动)

// 订单服务
public void createOrder(Order order) {
    // 1. 创建订单
    orderDao.insert(order);
    // 2. 发布OrderCreated事件
    eventBus.publish(new OrderCreatedEvent(order));
}

// 库存服务监听事件
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
    try {
        // 扣减库存
        inventoryService.decrease(event.getOrder());
        // 发布InventoryDecreased事件
        eventBus.publish(new InventoryDecreasedEvent(event.getOrder()));
    } catch (Exception e) {
        // 发布InventoryDecreaseFailed事件
        eventBus.publish(new InventoryDecreaseFailedEvent(event.getOrder()));
    }
}

// 订单服务监听失败事件
@EventListener
public void onInventoryDecreaseFailed(InventoryDecreaseFailedEvent event) {
    // 补偿:取消订单
    orderService.cancelOrder(event.getOrder().getId());
}

方式2:编排式Saga(中央协调器)

// Saga编排器
public class OrderSagaOrchestrator {
    
    public void executeOrderSaga(Order order) {
        try {
            // 步骤1:创建订单
            orderService.createOrder(order);
            
            // 步骤2:扣减库存
            inventoryService.decreaseStock(order);
            
            // 步骤3:扣减余额
            accountService.deductBalance(order);
            
            // 步骤4:发货
            shippingService.ship(order);
            
        } catch (Exception e) {
            // 执行补偿流程
            compensate(order, e);
        }
    }
    
    private void compensate(Order order, Exception cause) {
        // 倒序执行补偿
        try {
            shippingService.cancelShipping(order);
        } catch (Exception e) {}
        
        try {
            accountService.refund(order);
        } catch (Exception e) {}
        
        try {
            inventoryService.restoreStock(order);
        } catch (Exception e) {}
        
        try {
            orderService.cancelOrder(order);
        } catch (Exception e) {}
    }
}

优点

  • 实现相对简单
  • 不需要锁定资源
  • 适合长流程

缺点

  • 无法保证隔离性
  • 需要实现补偿逻辑
  • 补偿可能失败

适用场景:长流程、跨多个服务的业务(如旅游订单:机票+酒店+门票)

方案6:本地消息表

原理:利用本地事务保证业务操作和消息发送的原子性。

@Transactional
public void createOrder(Order order) {
    // 1. 创建订单(业务操作)
    orderDao.insert(order);
    
    // 2. 插入本地消息表(同一个本地事务)
    LocalMessage message = new LocalMessage();
    message.setContent(JSON.toJSONString(order));
    message.setStatus(MessageStatus.PENDING);
    messageDao.insert(message);
    
    // 本地事务保证以上两步的原子性
}

// 定时任务扫描消息表,发送待发送的消息
@Scheduled(fixedDelay = 1000)
public void sendPendingMessages() {
    List<LocalMessage> messages = messageDao.getPendingMessages();
    for (LocalMessage message : messages) {
        try {
            // 发送消息到MQ
            mqProducer.send(message.getContent());
            // 更新消息状态为已发送
            message.setStatus(MessageStatus.SENT);
            messageDao.update(message);
        } catch (Exception e) {
            // 发送失败,下次继续尝试
        }
    }
}

优点

  • 实现简单
  • 利用本地事务保证可靠性
  • 最终一致性保证

缺点

  • 与业务耦合
  • 需要额外的消息表
  • 消息可能重复(需要下游幂等)

适用场景:对一致性要求不太严格、可接受最终一致的场景

方案7:事务消息(RocketMQ)

原理:RocketMQ提供的事务消息机制。

public class OrderService {
    
    @Autowired
    private TransactionMQProducer producer;
    
    public void createOrder(Order order) {
        // 发送事务消息
        producer.sendMessageInTransaction(
            new Message("order-topic", JSON.toJSONString(order).getBytes()),
            order
        );
    }
    
    // 事务监听器
    @Component
    class OrderTransactionListener implements TransactionListener {
        
        // 执行本地事务
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            try {
                Order order = (Order) arg;
                // 执行本地事务
                orderDao.insert(order);
                return LocalTransactionState.COMMIT_MESSAGE;
            } catch (Exception e) {
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        }
        
        // 回查本地事务状态
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            // 查询本地事务是否执行成功
            String orderId = // 从消息中解析orderId
            Order order = orderDao.getById(orderId);
            if (order != null) {
                return LocalTransactionState.COMMIT_MESSAGE;
            }
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
}

优点

  • RocketMQ原生支持
  • 实现相对简单
  • 可靠性高

缺点

  • 依赖RocketMQ
  • 只能保证最终一致性

适用场景:使用RocketMQ的系统,需要保证消息和本地事务一致性

方案8:最大努力通知

原理:主动通知+定期校对。

public class OrderNotificationService {
    
    // 创建订单后通知下游
    public void notifyDownstream(Order order) {
        // 尝试通知(最多重试N次)
        int maxRetry = 3;
        for (int i = 0; i < maxRetry; i++) {
            try {
                restTemplate.postForObject(
                    "http://downstream/api/order",
                    order,
                    String.class
                );
                return; // 成功则返回
            } catch (Exception e) {
                // 失败则重试
                sleep(1000 * (i + 1)); // 递增延迟
            }
        }
        // 记录失败日志,人工介入或定期对账
        log.error("Failed to notify downstream after {} retries", maxRetry);
    }
    
    // 定期对账
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
    public void reconcile() {
        // 查询本地订单
        List<Order> localOrders = orderDao.getOrdersOfYesterday();
        // 查询下游订单
        List<Order> downstreamOrders = restTemplate.getForObject(
            "http://downstream/api/orders/yesterday",
            List.class
        );
        // 对比差异,补发通知
        // ...
    }
}

优点

  • 实现简单
  • 对下游系统要求低

缺点

  • 可靠性最低
  • 需要定期对账

适用场景:对一致性要求不高的场景(如通知类消息)

4. 分布式事务框架

Seata(阿里开源)

支持模式

  • AT模式:自动补偿的2PC(类似XA,但无锁)
  • TCC模式:手动补偿
  • Saga模式:状态机模式
  • XA模式:传统XA协议

AT模式示例

// 无需修改业务代码,只需加注解
@GlobalTransactional
public void createOrder(Order order) {
    orderService.create(order);           // 订单服务
    inventoryService.decrease(order);      // 库存服务
    accountService.deduct(order);          // 账户服务
}

5. 方案对比与选型

方案 一致性 性能 复杂度 适用场景
2PC/XA 强一致 金融、对一致性要求极高
TCC 最终一致 电商、对性能和一致性都有要求
Saga 最终一致 长流程、跨多服务
本地消息表 最终一致 简单场景、可接受最终一致
事务消息 最终一致 使用RocketMQ的系统
最大努力通知 弱一致 通知类场景

6. 总结

选型建议

  1. 金融支付:2PC/XA(强一致性)
  2. 电商下单:TCC或Seata AT模式(性能+一致性)
  3. 长流程:Saga模式
  4. 简单场景:本地消息表或事务消息
  5. 通知类:最大努力通知

最佳实践

  • 优先考虑业务层面避免分布式事务
  • 根据业务特性选择合适的一致性级别
  • 刚性事务优先选Seata AT,柔性事务优先选事务消息
  • 注意幂等性和补偿逻辑的设计