分布式事务Saga模式与TCC方案对比选型:微服务实战指南

分布式事务为什么难:从CAP到业务拆分的必然

后端开发中微服务架构的普及让分布式事务成为日常而非例外。一个电商下单流程横跨订单、库存、支付、积分四个服务,任何一个步骤失败都需要回滚或补偿。Spring Boot框架和Java/Go实战中,本地事务的ACID保证在跨服务场景下失效,高并发设计必须接受BASE约束——基本可用、软状态、最终一致性。分布式事务的解决方案不是消灭不一致,而是用架构手段把不一致窗口压缩到业务可接受的范围,同时保障服务治理的稳定性。

三种主流方案的定位:

  • 2PC/3PC:强一致性,同步阻塞,吞吐受限,适合传统数据库跨库事务
  • TCC:业务层补偿,每个参与者实现Try/Confirm/Cancel三个接口
  • Saga:长事务编排,正向执行+逆向补偿,最终一致性

Saga模式:编排式与协同式

Saga模式将长事务拆分为多个本地事务序列,每个本地事务完成后发送事件触发下一个步骤,失败时执行反向补偿事务。关键区别在于编排方式。

编排式Saga(Orchestration)

由中央协调器(Saga Orchestrator)统一控制事务流程,各参与者只暴露标准接口。适合复杂业务流程和消息中间件集成场景。

// Saga编排器 - 订单创建流程
@Service
public class CreateOrderSaga {

    private final SagaDefinition<CreateOrderState> definition;

    public CreateOrderSaga(OrderServiceClient orderClient,
                           InventoryServiceClient inventoryClient,
                           PaymentServiceClient paymentClient,
                           PointServiceClient pointClient) {
        this.definition = SagaDefinition
            .step("reserve-inventory")
                .invokeParticipant(order -> inventoryClient.reserve(order.getSkus()))
                .withCompensation(order -> inventoryClient.release(order.getSkus()))
            .step("create-payment")
                .invokeParticipant(order -> paymentClient.charge(order.getAmount()))
                .withCompensation(order -> paymentClient.refund(order.getPaymentId()))
            .step("deduct-points")
                .invokeParticipant(order -> pointClient.deduct(order.getPoints()))
                .withCompensation(order -> pointClient.addBack(order.getPoints()))
            .step("confirm-order")
                .invokeParticipant(order -> orderClient.confirm(order.getId()))
                .withCompensation(order -> orderClient.reject(order.getId()))
            .build();
    }

    public SagaDefinition<CreateOrderState> getDefinition() {
        return definition;
    }
}

协同式Saga(Choreography)

各服务通过事件驱动自主决策,无中央协调器。适合简单流程,但业务逻辑分散在各服务中,调试和追踪困难。

// 协同式Saga - Go实现
// 订单服务
func (s *OrderService) CreateOrder(ctx context.Context, cmd CreateOrderCmd) error {
    order := NewOrder(cmd)
    if err := s.orderRepo.Save(ctx, order); err != nil {
        return err
    }
    // 发布订单创建事件,触发下游服务
    return s.eventBus.Publish(ctx, OrderCreatedEvent{
        OrderID:  order.ID,
        SKUs:     cmd.SKUs,
        Amount:   cmd.Amount,
        Points:   cmd.Points,
    })
}

// 库存服务 - 订阅订单创建事件
func (h *InventoryHandler) HandleOrderCreated(ctx context.Context, evt OrderCreatedEvent) error {
    err := h.inventorySvc.Reserve(ctx, evt.SKUs)
    if err != nil {
        // 库存预留失败,发布补偿事件
        h.eventBus.Publish(ctx, InventoryReserveFailedEvent{OrderID: evt.OrderID})
        return err
    }
    return h.eventBus.Publish(ctx, InventoryReservedEvent{OrderID: evt.OrderID})
}

TCC模式:业务层补偿的三阶段

TCC(Try-Confirm-Cancel)要求每个参与者实现三个接口,协调器统一调用。与Saga的最大区别是Try阶段预留资源但不提交,Confirm阶段才真正生效,Cancel阶段释放预留资源。业务中台建设中,TCC适合资金类、库存类等对一致性要求高的场景。

Try/Confirm/Cancel接口设计

// TCC参与者接口定义
public interface TccParticipant<T> {
    /** 预留资源,冻结但不提交 */
    TryResult tryPhase(T params);

    /** 确认提交,消费预留资源 */
    ConfirmResult confirmPhase(String xid);

    /** 取消回滚,释放预留资源 */
    CancelResult cancelPhase(String xid);
}

// 库存TCC实现
@Component
public class InventoryTccParticipant implements TccParticipant<ReserveCmd> {

    @Override
    public TryResult tryPhase(ReserveCmd cmd) {
        // 冻结库存:从可用库存扣减,写入冻结记录
        int affected = inventoryMapper.freezeStock(
            cmd.getSkuId(), cmd.getQuantity(), cmd.getXid()
        );
        if (affected == 0) {
            return TryResult.fail("库存不足");
        }
        return TryResult.success(cmd.getXid());
    }

    @Override
    public ConfirmResult confirmPhase(String xid) {
        // 确认扣减:删除冻结记录,库存已扣减生效
        int affected = inventoryMapper.confirmFrozen(xid);
        return affected > 0 ? ConfirmResult.success() : ConfirmResult.fail("冻结记录不存在");
    }

    @Override
    public CancelResult cancelPhase(String xid) {
        // 释放冻结库存:恢复可用库存,删除冻结记录
        int affected = inventoryMapper.releaseFrozen(xid);
        return affected > 0 ? CancelResult.success() : CancelResult.fail("冻结记录不存在");
    }
}

TCC协调器实现

@Service
public class TccCoordinator {

    private final List<TccParticipant<?>> participants;
    private final TransactionLogRepository logRepo;

    public CoordinationResult execute(TccTransaction tx) {
        String xid = generateXid();
        List<String> confirmed = new ArrayList<>();

        // Phase 1: Try
        for (TccParticipant<Object> participant : tx.getParticipants()) {
            TryResult result = participant.tryPhase(tx.getParams());
            if (!result.isSuccess()) {
                // Try失败,回滚已成功的参与者
                rollback(xid, confirmed);
                return CoordinationResult.rollback(result.getReason());
            }
            confirmed.add(participant.getName());
        }

        // Phase 2: Confirm
        for (String name : confirmed) {
            TccParticipant<?> p = findParticipant(name);
            ConfirmResult result = p.confirmPhase(xid);
            if (!result.isSuccess()) {
                // Confirm失败需要人工介入(不应发生)
                logRepo.markAsHeuristic(xid, name);
                return CoordinationResult.heuristic(xid, name);
            }
        }

        return CoordinationResult.commit();
    }

    private void rollback(String xid, List<String> confirmed) {
        for (String name : confirmed) {
            try {
                findParticipant(name).cancelPhase(xid);
            } catch (Exception e) {
                logRepo.markAsCompensationFailed(xid, name);
            }
        }
    }
}

Saga vs TCC:场景化选型指南

核心差异对比

维度 Saga(编排式) TCC
一致性模型 最终一致 准强一致(Try预留资源)
隔离性 无隔离(脏读可见) Try阶段隔离
回滚方式 补偿事务(正向操作的反操作) Cancel释放预留
性能 高(无资源锁定) 中等(Try阶段锁定资源)
实现复杂度 低(标准接口) 高(三个接口+冻结逻辑)
适用场景 跨服务编排、长流程 资金、库存等强一致场景

选型决策树

def choose_distributed_tx_strategy(requirements):
    """分布式事务方案选型决策"""
    if requirements.get('strong_consistency'):
        if requirements.get('allow_resource_freeze'):
            return 'TCC'
        else:
            return '2PC (仅限同数据库)'

    if requirements.get('long_running') or requirements.get('cross_service'):
        if requirements.get('complex_flow'):
            return 'Saga_Orchestration'
        else:
            return 'Saga_Choreography'

    # 默认推荐
    return 'Saga_Orchestration'

# 典型场景判断
scenarios = {
    "电商下单(库存+支付)": choose_distributed_tx_strategy({
        "strong_consistency": True,
        "allow_resource_freeze": True
    }),  # → TCC

    "旅行预订(机票+酒店+租车)": choose_distributed_tx_strategy({
        "long_running": True,
        "cross_service": True,
        "complex_flow": True
    }),  # → Saga编排式

    "积分兑换(扣积分+发货)": choose_distributed_tx_strategy({
        "cross_service": True,
        "complex_flow": False
    }),  # → Saga协同式
}

消息中间件保障:至少一次交付与幂等性

无论选择Saga还是TCC,消息中间件的可靠性都是分布式事务的基础设施。API接口规范要求服务间通信必须保证”至少一次交付”(at-least-once),消费端必须实现幂等性。

RocketMQ事务消息实现

@Service
public class OrderTransactionListener implements TransactionListener {

    @Autowired
    private OrderService orderService;

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            String orderId = msg.getKeys();
            orderService.confirmOrder(orderId);
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        String orderId = msg.getKeys();
        Order order = orderService.getById(orderId);
        if (order == null) {
            return LocalTransactionState.UNKNOW; // 继续回查
        }
        return order.isConfirmed()
            ? LocalTransactionState.COMMIT_MESSAGE
            : LocalTransactionState.ROLLBACK_MESSAGE;
    }
}

// 发送事务消息
@Transactional
public void createOrderWithTransaction(CreateOrderCmd cmd) {
    Order order = orderService.create(cmd);

    Message msg = new Message(
        "ORDER_TOPIC",
        "TAG_CREATE",
        order.getId().getBytes(),
        order.getId().getBytes()  // key用于幂等
    );

    transactionMQProducer.sendMessageInTransaction(msg, null);
}

消费端幂等性保障

@Component
@RocketMQMessageListener(
    topic = "ORDER_TOPIC",
    consumerGroup = "inventory-consumer-group"
)
public class OrderMessageConsumer implements RocketMQListener<Message> {

    @Autowired
    private IdempotentService idempotentService;

    @Override
    public void onMessage(Message message) {
        String msgId = message.getKeys();

        // 幂等检查:已处理则跳过
        if (idempotentService.isProcessed(msgId)) {
            return;
        }

        // 执行业务逻辑
        try {
            inventoryService.process(message);
            idempotentService.markProcessed(msgId);
        } catch (Exception e) {
            // 业务失败,等待重试
            throw new RuntimeException(e);
        }
    }
}

服务治理与监控:分布式事务可观测性

微服务架构下分布式事务的调试和监控依赖分布式追踪。W3C Trace Context标准配合OpenTelemetry,可以在全局视角追踪一笔事务的完整路径:

# OpenTelemetry Collector配置
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  jaeger:
    endpoint: jaeger-collector:14250
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "dist_tx"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

分布式事务没有完美方案,只有合适方案。Saga适合长流程、松耦合场景,TCC适合资金库存等强一致场景。实际生产中两种方案经常混用——核心资金链路用TCC,周边积分通知用Saga。选型的本质是回答一个问题:不一致窗口业务能否容忍?能容忍就选Saga,不能就选TCC。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-saga-mo-shi-yu-tcc-fang-an-dui-bi-xuan/

(0)
小编小编
上一篇 12小时前
下一篇 12小时前

相关推荐