分布式事务实战:Saga、TCC与最终一致性模式详解

By | 2026年6月26日

在微服务架构中,分布式事务是绕不开的难题。传统数据库的ACID事务无法跨服务,因此需要引入特殊的模式来保证数据最终一致性。本文通过实战代码,对比三种常见模式:Saga、TCC和最终一致性(基于消息队列)。

1. 场景设定

假设一个电商下单流程:

  • 订单服务(Order Service):创建订单
  • 库存服务(Inventory Service):扣减库存
  • 支付服务(Payment Service):扣款

我们需要保证这三步要么全部成功,要么全部回滚(或补偿)。

2. Saga模式

Saga是一种长事务模式,将大事务拆分为一系列本地事务,每个本地事务都有对应的补偿操作。Saga分为两种编排方式:编排(Choreography)协调(Orchestration)

2.1 编排式Saga(基于事件)

服务之间通过事件驱动,每个服务在完成操作后发布事件,触发下一个服务。

代码示例(Node.js + RabbitMQ)


// order-service/index.js
const amqp = require('amqplib');

async function createOrder(order) {
    // 1. 创建订单(本地事务)
    await db.orders.insert(order);
    
    // 2. 发布“订单创建”事件
    const conn = await amqp.connect('amqp://localhost');
    const channel = await conn.createChannel();
    channel.publish('exchange', 'order.created', Buffer.from(JSON.stringify(order)));
    
    // 3. 等待后续事件(如库存扣减成功/失败)
    // 实际应用中会监听回复队列
}

// inventory-service/index.js
const amqp = require('amqplib');

async function handleOrderCreated(msg) {
    const order = JSON.parse(msg.content.toString());
    try {
        // 扣减库存
        await db.inventory.decrement(order.productId, order.quantity);
        // 发布库存扣减成功事件
        channel.publish('exchange', 'inventory.deducted', Buffer.from(JSON.stringify({ orderId: order.id })));
    } catch (err) {
        // 发布库存扣减失败事件,触发补偿
        channel.publish('exchange', 'inventory.deduct.failed', Buffer.from(JSON.stringify({ orderId: order.id })));
    }
}

补偿操作:如果后续步骤失败(如支付失败),则需发布“库存补偿”事件,库存服务监听后恢复库存。

优点:松耦合,服务自治。 缺点:事件流复杂,难以追踪;补偿逻辑分散。

2.2 协调式Saga(基于协调器)

引入一个协调器(Saga Orchestrator)负责发送指令给各个服务。

代码示例(Java + Spring Boot)


// SagaOrchestrator.java
@Component
public class SagaOrchestrator {
    
    @Autowired
    private OrderService orderService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;

    @Transactional
    public void createOrderSaga(OrderRequest request) {
        // 步骤1:创建订单
        Order order = orderService.create(request);
        try {
            // 步骤2:扣减库存
            inventoryService.deduct(order.getProductId(), order.getQuantity());
        } catch (Exception e) {
            // 补偿:取消订单
            orderService.cancel(order.getId());
            throw new SagaException("库存不足");
        }
        try {
            // 步骤3:扣款
            paymentService.debit(order.getUserId(), order.getAmount());
        } catch (Exception e) {
            // 补偿:恢复库存 + 取消订单
            inventoryService.addBack(order.getProductId(), order.getQuantity());
            orderService.cancel(order.getId());
            throw new SagaException("扣款失败");
        }
    }
}

优点:逻辑集中,易于管理。 缺点:协调器可能成为单点。

3. TCC模式

TCC(Try-Confirm-Cancel)是一种补偿型事务模式,分为三个阶段:

  1. Try:预留资源(如锁定库存)
  2. Confirm:确认执行(如扣减库存)
  3. Cancel:取消(释放预留资源)

代码示例(Java + Spring Boot)


// InventoryServiceTCC.java
@Service
public class InventoryServiceTCC {
    
    @Autowired
    private InventoryRepository repository;

    // Try:预留库存
    @Transactional
    public boolean tryDeduct(Long productId, Integer quantity, String txId) {
        // 检查库存是否足够
        Inventory inventory = repository.findByProductId(productId);
        if (inventory.getAvailable() < quantity) {
            return false;
        }
        // 锁定库存:减少可用库存,增加锁定库存
        inventory.setAvailable(inventory.getAvailable() - quantity);
        inventory.setLocked(inventory.getLocked() + quantity);
        repository.save(inventory);
        // 记录事务日志
        saveTransactionLog(txId, productId, quantity, "TRY");
        return true;
    }

    // Confirm:确认扣减(真正扣减锁定库存)
    @Transactional
    public boolean confirmDeduct(String txId) {
        TransactionLog log = getTransactionLog(txId);
        if (log == null || log.getStatus() != "TRY") {
            return false;
        }
        Inventory inventory = repository.findByProductId(log.getProductId());
        inventory.setLocked(inventory.getLocked() - log.getQuantity());
        repository.save(inventory);
        log.setStatus("CONFIRMED");
        updateTransactionLog(log);
        return true;
    }

    // Cancel:取消预留(释放锁定库存)
    @Transactional
    public boolean cancelDeduct(String txId) {
        TransactionLog log = getTransactionLog(txId);
        if (log == null || log.getStatus() != "TRY") {
            return false;
        }
        Inventory inventory = repository.findByProductId(log.getProductId());
        inventory.setAvailable(inventory.getAvailable() + log.getQuantity());
        inventory.setLocked(inventory.getLocked() - log.getQuantity());
        repository.save(inventory);
        log.setStatus("CANCELLED");
        updateTransactionLog(log);
        return true;
    }
}

注意:TCC需要业务侵入性强,每个服务都要实现Try/Confirm/Cancel接口。同时要保证幂等性。

适用场景:短事务,对一致性要求高(如金融)。

4. 最终一致性(基于消息队列)

利用消息队列(如RocketMQ、Kafka)实现异步确保。核心思想:本地事务 + 消息发送保证最终一致性。

代码示例(Spring Boot + RocketMQ)


// OrderService.java
@Service
public class OrderService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Transactional
    public void createOrder(OrderRequest request) {
        // 1. 本地创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setAmount(request.getAmount());
        order.setStatus("PENDING");
        orderRepository.save(order);
        
        // 2. 发送半消息(RocketMQ事务消息)
        rocketMQTemplate.sendMessageInTransaction(
            "order-tx-producer",
            MessageBuilder.withPayload(order).build(),
            order
        );
    }

    // 事务消息监听器
    @RocketMQTransactionListener(producerGroup = "order-tx-producer")
    class OrderTransactionListener implements RocketMQLocalTransactionListener {
        
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            // 执行本地事务(扣减库存等)
            try {
                // 这里可以调用库存服务(通过RPC或消息)
                inventoryService.deduct(...);
                return RocketMQLocalTransactionState.COMMIT;
            } catch (Exception e) {
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }

        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            // 回查订单状态
            Order order = (Order) msg.getPayload();
            Order dbOrder = orderRepository.findById(order.getId());
            if ("SUCCESS".equals(dbOrder.getStatus())) {
                return RocketMQLocalTransactionState.COMMIT;
            } else if ("FAILED".equals(dbOrder.getStatus())) {
                return RocketMQLocalTransactionState.ROLLBACK;
            }
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}

优点:性能高,业务解耦。 缺点:最终一致性有延迟;需要处理消息重复。

5. 模式对比与选型

| 模式 | 一致性 | 性能 | 业务侵入 | 适用场景 | |——|——–|——|———-|———-| | Saga(编排) | 最终 | 高 | 低 | 长事务,事件驱动 | | Saga(协调) | 最终 | 中 | 中 | 复杂流程控制 | | TCC | 强(准实时) | 低 | 高 | 金融、短事务 | | 最终一致性(消息) | 最终 | 高 | 低 | 异步场景,如订单 |

选型建议

  • 如果业务允许短暂不一致,优先选择最终一致性(消息队列)。
  • 如果对一致性要求高且短事务,选TCC。
  • 如果事务跨度长,选Saga。

6. 最佳实践

  1. 幂等性:所有补偿/确认操作必须幂等。
  2. 事务日志:记录每个步骤的状态,便于恢复。
  3. 超时处理:设置合理的超时,配合重试机制。
  4. 监控与告警:监控事务执行状态,失败时及时告警。

7. 总结

本文通过实战代码演示了Saga、TCC和最终一致性三种分布式事务模式。没有银弹,需要根据业务场景选择。建议从简单的最终一致性入手,随着业务复杂度增加再考虑Saga或TCC。