后端接口的 “数据一致性” 保障:分布式事务的实践之道

82 阅读4分钟

在分布式系统中,跨服务操作(如 “下单减库存” 涉及订单服务和库存服务)容易出现 “数据不一致” 问题 —— 订单创建成功但库存未扣减,或库存扣减后订单创建失败。分布式事务技术通过协调多个服务的操作,确保 “要么全成功,要么全回滚”,是保障数据一致性的核心手段。

分布式事务的核心挑战

分布式事务的核心是解决 “跨服务操作的原子性”,面临的挑战:

  • 网络不可靠:服务间通信可能超时、丢包
  • 服务故障:某服务执行中宕机,无法感知其他服务状态
  • 性能损耗:事务协调会增加系统开销,影响吞吐量

主流分布式事务方案

1. 2PC(两阶段提交):强一致性但性能差

2PC 是传统分布式事务方案,分为 “准备阶段” 和 “提交阶段”,由事务协调者(Coordinator)协调所有参与者(Participant)。

流程

  1. 准备阶段:协调者向所有参与者发送 “准备提交” 请求,参与者执行操作但不提交,返回 “就绪” 或 “失败”

  2. 提交阶段:若所有参与者都就绪,协调者发送 “提交” 请求;任一参与者失败,则发送 “回滚” 请求

代码示例

// 协调者逻辑
public boolean createOrderAndDeductStock(OrderDTO order) {
    // 1. 初始化事务协调者
    TwoPhaseCommitCoordinator coordinator = new TwoPhaseCommitCoordinator();
    
    // 2. 注册参与者(订单服务、库存服务)
    OrderParticipant orderParticipant = new OrderParticipant(order);
    StockParticipant stockParticipant = new StockParticipant(order.getProductId(), order.getNum());
    coordinator.registerParticipants(orderParticipant, stockParticipant);
    
    // 3. 执行两阶段提交
    return coordinator.commit();
}

// 参与者接口
public interface Participant {
    boolean prepare(); // 准备阶段:执行操作,记录日志
    boolean commit();  // 提交阶段:确认操作
    boolean rollback(); // 回滚阶段:撤销操作
}

优缺点

  • 优点:强一致性,适合对一致性要求极高的场景(如金融交易)
  • 缺点:性能差(阻塞等待)、协调者单点风险、长时间锁资源

2. TCC(Try-Confirm-Cancel):高性能的补偿型方案

TCC 将分布式事务拆分为三个阶段,由业务代码实现具体的确认和取消逻辑,更灵活且性能更高。

流程

  1. Try 阶段:资源检查并预留(如检查库存是否充足,冻结部分库存)

  2. Confirm 阶段:确认执行业务(如扣减冻结的库存,创建订单)

  3. Cancel 阶段:取消操作(如解冻冻结的库存,删除订单草稿)

代码示例(订单 + 库存 TCC)

// 订单服务TCC接口
public interface OrderTCCService {
    // Try:创建订单草稿
    @Transactional
    String tryCreateOrder(OrderDTO order);
    
    // Confirm:确认创建订单(正式生效)
    @Transactional
    void confirmCreateOrder(String txId);
    
    // Cancel:取消订单(删除草稿)
    @Transactional
    void cancelCreateOrder(String txId);
}

// 库存服务TCC接口
public interface StockTCCService {
    // Try:检查并冻结库存
    @Transactional
    String tryDeductStock(Long productId, int num);
    
    // Confirm:扣减冻结的库存
    @Transactional
    void confirmDeductStock(String txId);
    
    // Cancel:解冻库存
    @Transactional
    void cancelDeductStock(String txId);
}

// 业务协调逻辑
@Service
public class OrderTransactionService {
    @Autowired
    private OrderTCCService orderTCC;
    @Autowired
    private StockTCCService stockTCC;
    @Autowired
    private TransactionCoordinator coordinator; // 事务协调器(记录各阶段状态)
    
    public boolean createOrder(OrderDTO order) {
        String txId = UUID.randomUUID().toString();
        try {
            // 1. Try阶段:调用所有服务的try方法
            String orderTryResult = orderTCC.tryCreateOrder(order);
            String stockTryResult = stockTCC.tryDeductStock(order.getProductId(), order.getNum());
            
            // 2. 若所有Try成功,进入Confirm阶段
            if ("SUCCESS".equals(orderTryResult) && "SUCCESS".equals(stockTryResult)) {
                coordinator.markAsConfirming(txId);
                orderTCC.confirmCreateOrder(txId);
                stockTCC.confirmDeductStock(txId);
                coordinator.markAsCompleted(txId);
                return true;
            } else {
                // 3. 任一Try失败,进入Cancel阶段
                throw new RuntimeException("Try阶段失败,触发回滚");
            }
        } catch (Exception e) {
            coordinator.markAsCanceling(txId);
            orderTCC.cancelCreateOrder(txId);
            stockTCC.cancelDeductStock(txId);
            coordinator.markAsCancelled(txId);
            return false;
        }
    }
}

优缺点

  • 优点:性能高(无锁阻塞)、灵活性强(业务自定义补偿逻辑)
  • 缺点:侵入性强(需改造业务代码)、补偿逻辑复杂(如 Confirm/Cancel 的幂等性)

3. 最终一致性方案:适合高并发场景

对于一致性要求不高的场景(如订单创建后发送通知),可采用 “本地消息表 + 消息队列” 实现最终一致性:

流程

  1. 本地事务:执行主操作(如创建订单),并将 “发送通知” 的消息写入本地消息表

  2. 消息投递:定时任务扫描本地消息表,将未发送的消息投递到消息队列

  3. 消息消费:通知服务消费消息,执行发送操作;失败则重试,直到成功

优势:实现简单,性能高;劣势:存在短暂的数据不一致(如订单已创建但通知未发送)

方案选择策略

一致性要求性能要求推荐方案典型场景
强一致性2PC金融转账、支付结算
高一致性TCC订单创建 + 库存扣减、秒杀
最终一致性本地消息表 + MQ通知发送、日志同步

分布式事务没有 “银弹”,选择方案时需在 “一致性”“性能”“复杂度” 之间权衡。对于大多数业务场景,“最终一致性 + 补偿机制” 是更务实的选择 —— 既能保证数据最终正确,又能兼顾系统的吞吐量和可维护性。