分布式事务实战:Saga、TCC与最终一致性方案深度解析

By | 2026年9月8日

分布式事务实战:Saga、TCC与最终一致性方案深度解析

在微服务架构盛行的今天,分布式事务是每个后端工程师绕不开的难题。单体应用中的本地事务(ACID)在拆分为多个服务后,如何保证数据一致性?本文将结合电商下单的真实场景,深入浅出地讲解Saga、TCC和最终一致性三种模式,并给出可运行的代码示例和选型建议。

为什么需要分布式事务?

假设你正在开发一个电商平台,用户下单后需要:

  1. 创建订单(订单服务)
  2. 扣减库存(库存服务)
  3. 扣减余额(账户服务)

这三个操作分别由不同的微服务提供,如果其中一个失败,如何保证整体数据的一致性?传统的本地事务无法跨服务,因此需要分布式事务方案。

分布式事务的三大模式

一、Saga模式:基于补偿的事务编排

Saga模式将一个长事务拆分为多个本地事务,每个本地事务都有对应的补偿操作。如果后续步骤失败,则执行补偿操作回滚。

Saga的两种实现方式

  • Choreography(编排):服务间通过事件驱动,每个服务监听事件并执行相应操作或补偿。
  • Orchestration(编排):由一个中心协调器(Saga Execution Coordinator)负责指挥各个服务执行操作和补偿。

实战:使用Orchestration实现下单Saga

我们使用Spring Boot + Java来演示。首先定义订单服务、库存服务、账户服务的接口。


// 订单服务
public interface OrderService {
    Order createOrder(Long userId, Long productId, int amount);
    void compensateOrder(Long orderId);
}

// 库存服务
public interface StockService {
    void deductStock(Long productId, int quantity);
    void compensateStock(Long productId, int quantity);
}

// 账户服务
public interface AccountService {
    void deductBalance(Long userId, BigDecimal amount);
    void compensateBalance(Long userId, BigDecimal amount);
}

接下来实现Saga协调器:


@Component
public class OrderSagaCoordinator {
    
    @Autowired
    private OrderService orderService;
    @Autowired
    private StockService stockService;
    @Autowired
    private AccountService accountService;

    public void createOrderSaga(OrderRequest request) {
        // 步骤1:创建订单
        Order order = orderService.createOrder(request.getUserId(), request.getProductId(), request.getAmount());
        try {
            // 步骤2:扣减库存
            stockService.deductStock(request.getProductId(), request.getAmount());
            try {
                // 步骤3:扣减余额
                accountService.deductBalance(request.getUserId(), request.getPrice());
            } catch (Exception e) {
                // 步骤3失败,补偿库存
                stockService.compensateStock(request.getProductId(), request.getAmount());
                throw e;
            }
        } catch (Exception e) {
            // 步骤2失败或步骤3补偿后,补偿订单
            orderService.compensateOrder(order.getId());
            throw e;
        }
    }
}

注意:Saga模式没有隔离性,可能会有脏读。例如:在扣库存后、扣余额前,其他事务可能看到库存已扣但余额未扣。解决方式通常使用语义锁或业务上的幂等设计。

二、TCC模式:Try-Confirm-Cancel

TCC将每个服务操作拆分为两个阶段:Try阶段检查并预留资源,Confirm阶段执行真正提交,Cancel阶段释放预留资源。

TCC的三种角色

  • 主业务服务:发起整个分布式事务。
  • 从业务服务:提供Try/Confirm/Cancel接口。
  • 事务管理器:协调各从业务服务的执行。

实战:使用TCC-Transaction框架

这里使用开源框架tcc-transaction,但为了演示原理,我们手写一个简单的TCC实现。


// 库存服务TCC接口
public interface StockServiceTcc {
    void tryDeductStock(Long productId, int quantity);
    void confirmDeductStock(Long productId, int quantity);
    void cancelDeductStock(Long productId, int quantity);
}

// 实现类
@Service
public class StockServiceTccImpl implements StockServiceTcc {
    
    @Override
    public void tryDeductStock(Long productId, int quantity) {
        // 检查库存是否充足,并扣减可用库存(冻结库存)
        // update stock set frozen_quantity = frozen_quantity + ? where product_id = ? and available_quantity >= ?
        // 如果影响行数为0,则抛出异常
    }

    @Override
    public void confirmDeductStock(Long productId, int quantity) {
        // 真正扣减库存:将冻结库存转为已扣减
        // update stock set frozen_quantity = frozen_quantity - ?, available_quantity = available_quantity - ? where product_id = ?
    }

    @Override
    public void cancelDeductStock(Long productId, int quantity) {
        // 释放冻结库存
        // update stock set frozen_quantity = frozen_quantity - ? where product_id = ?
    }
}

事务管理器协调代码:


public class TccTransactionManager {
    
    public void executeTcc(TransactionContext context) {
        // 1. 执行所有Try
        for (TccAction action : context.getActions()) {
            action.tryMethod();
        }
        // 2. 根据Try结果决定Confirm或Cancel
        boolean success = true;
        try {
            // 模拟业务逻辑,如果全部成功则确认,否则取消
            if (success) {
                for (TccAction action : context.getActions()) {
                    action.confirmMethod();
                }
            } else {
                for (TccAction action : context.getActions()) {
                    action.cancelMethod();
                }
            }
        } catch (Exception e) {
            // 如果Confirm或Cancel失败,需要重试或人工介入
        }
    }
}

注意:TCC要求每个服务实现三个接口,开发成本较高。且Confirm和Cancel需要保证幂等性,因为可能被重复调用。

三、最终一致性:基于消息队列

最终一致性方案通常使用消息队列(如RocketMQ、Kafka)实现。核心思想是将本地事务与消息发送绑定,通过消息异步通知其他服务,保证最终一致。

经典实现:本地消息表 + 消息队列

  1. 在业务数据库中创建消息表。
  2. 在本地事务中,同时写入业务数据和消息记录。
  3. 通过定时任务或消息队列发送消息。

实战:使用RocketMQ事务消息

RocketMQ原生支持事务消息,我们使用它来演示。


// 订单服务中发送事务消息
public class OrderServiceWithMq {
    
    @Autowired
    private TransactionMQProducer producer;

    public void createOrder(OrderRequest request) {
        // 1. 创建订单(本地事务)
        orderDao.create(request);
        // 2. 发送事务消息
        Message msg = new Message("ORDER_TOPIC", "TAG", request.getOrderId().getBytes());
        TransactionSendResult result = producer.sendMessageInTransaction(msg, request);
        // 根据result状态决定后续操作
    }

    // 本地事务执行器
    @Transactional
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        OrderRequest request = (OrderRequest) arg;
        try {
            // 执行本地事务(创建订单)
            orderDao.create(request);
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    // 消息回查
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 根据订单号查询订单是否存在,决定commit或rollback
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

库存服务监听消息并扣减库存:


@Component
public class StockConsumer {
    
    @KafkaListener(topics = "ORDER_TOPIC")
    public void onMessage(String orderId) {
        // 幂等处理:检查是否已消费
        if (!isProcessed(orderId)) {
            // 扣减库存
            stockDao.deduct(orderId);
            // 标记已处理
            markProcessed(orderId);
        }
    }
}

注意:最终一致性方案无法保证实时一致性,只适用于对实时性要求不高的场景。且消费方需要做幂等处理,防止重复消费。

三大模式对比与选型

| 模式 | 一致性 | 隔离性 | 开发成本 | 适用场景 | |——|——–|——–|———-|———-| | Saga | 最终一致 | 无 | 中 | 长事务、业务流程复杂 | | TCC | 强一致 | 有 | 高 | 对一致性要求高、短事务 | | 最终一致性 | 最终一致 | 无 | 低 | 异步场景、高并发 |

选型建议:

  • 如果业务对一致性要求高(如支付),且事务较短,优先选TCC。
  • 如果业务流程长,且每一步可以异步,选Saga。
  • 如果允许数据延迟一致,选最终一致性方案,成本最低。

踩坑经验

  1. 空回滚与悬挂:在Saga中,补偿操作可能在原操作未执行时就触发(例如网络超时后重试),需要设计空补偿逻辑。
  2. 幂等性:所有补偿和确认操作都必须是幂等的,因为可能被多次调用。
  3. 事务消息的可靠性:使用消息队列时,要确保生产者本地事务和消息发送的一致性,RocketMQ事务消息解决了这个问题。

总结

分布式事务没有银弹,选择哪种方案取决于业务场景和一致性要求。Saga适合长流程,TCC适合强一致短事务,最终一致性适合高并发异步场景。实践中,应结合幂等设计、异常处理机制,并做好监控和告警。

延伸阅读:

  • 《分布式系统设计》
  • 阿里巴巴Seata框架
  • 微服务架构下的数据一致性实践