微服务架构下分布式事务解决方案技术预研:Saga模式vsTCC模式对比分析

D
dashen87 2025-09-24T14:25:37+08:00
0 0 221

微服务架构下分布式事务解决方案技术预研:Saga模式 vs TCC模式对比分析

引言:微服务架构中的分布式事务挑战

在现代软件系统中,微服务架构已成为主流的系统设计范式。它通过将大型单体应用拆分为多个独立部署、可独立扩展的服务单元,提升了系统的灵活性、可维护性和开发效率。然而,这种“按业务边界划分服务”的设计理念也带来了新的技术挑战——分布式事务管理

在传统的单体架构中,所有业务逻辑运行在一个进程中,数据库操作可以通过本地事务(如 JDBC 的 Connection.commit())轻松实现原子性、一致性、隔离性和持久性(ACID)。但在微服务架构中,一个完整的业务流程往往涉及多个服务之间的调用,每个服务可能拥有自己的数据库或数据存储,这就导致了跨服务的数据一致性问题。

例如,在电商系统中,“下单”这一业务流程通常包含以下步骤:

  1. 库存服务:扣减商品库存;
  2. 订单服务:创建订单记录;
  3. 支付服务:发起支付请求。

如果上述任何一个环节失败,而前序操作已经提交,就会造成数据不一致(比如库存已扣但订单未创建),严重影响用户体验和企业信誉。

因此,如何在微服务环境下保证跨服务操作的一致性,成为架构设计的核心难题之一。目前业界广泛采用的分布式事务解决方案主要包括:两阶段提交(2PC)消息队列+最终一致性Saga 模式TCC 模式

其中,Saga 模式TCC 模式 因其对性能的友好性与实际落地可行性,被越来越多的企业采纳。本文将深入剖析这两种模式的实现原理、适用场景、性能特点,并结合真实业务案例进行对比分析,为技术选型提供决策依据。

一、Saga 模式:长事务的补偿机制

1.1 基本概念与核心思想

Saga 模式是一种用于处理长时间运行的分布式事务的解决方案,其核心思想是:将一个大的事务拆分为多个本地事务,每个本地事务对应一个服务的操作,当某一步失败时,通过执行一系列“补偿操作”来回滚之前已完成的所有操作。

Saga 模式有两种主要变体:

  • Choreography(编排型):各服务之间通过事件驱动的方式协作,由外部协调器(如消息中间件)触发后续步骤。
  • Orchestration(编排型):存在一个中心化的协调者(Orchestrator),负责控制整个 Saga 流程。

✅ 推荐使用 Orchestration 模式,便于调试、监控和异常处理。

1.2 工作流程示例

以“下单”为例,使用 Saga 模式的工作流如下:

[开始]
   ↓
库存服务:扣减库存 → 成功?
   ↓ 是
   ↓
订单服务:创建订单 → 成功?
   ↓ 是
   ↓
支付服务:发起支付 → 成功?
   ↓ 是
   ↓
[成功结束]
   ↓ 否
   ↓
支付失败 → 执行补偿:订单服务回滚订单 → 库存服务恢复库存

若任一步骤失败,则从后往前依次执行补偿操作,直到状态恢复到一致。

1.3 补偿机制的设计原则

  • 幂等性:补偿操作必须是幂等的,避免重复执行导致错误。
  • 可逆性:每一步正向操作都应有对应的反向操作。
  • 异步化:补偿操作建议异步执行,防止阻塞主流程。
  • 持久化状态:需要持久化当前 Saga 的执行状态(如数据库记录),以便故障恢复。

1.4 实现代码示例(基于 Spring Boot + Kafka)

我们构建一个简单的订单 Saga 示例,使用 Orchestration 模式,由一个协调服务统一调度。

1.4.1 依赖引入(pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

1.4.2 Saga 状态实体(OrderSaga.java)

@Entity
@Table(name = "order_saga")
public class OrderSaga {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderId;
    private String status; // INIT, IN_PROGRESS, SUCCESS, FAILED, COMPENSATING

    private String currentStep;
    private LocalDateTime startTime;
    private LocalDateTime endTime;

    // Getters and Setters
}

1.4.3 协调服务(SagaCoordinatorService.java)

@Service
@RequiredArgsConstructor
public class SagaCoordinatorService {

    private final OrderSagaRepository sagaRepo;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public void startOrderSaga(String orderId) {
        var saga = new OrderSaga();
        saga.setOrderId(orderId);
        saga.setStatus("IN_PROGRESS");
        saga.setCurrentStep("RESERVE_STOCK");
        saga.setStartTime(LocalDateTime.now());
        sagaRepo.save(saga);

        // 发送事件启动库存服务
        kafkaTemplate.send("stock-topic", Map.of(
            "action", "reserve",
            "orderId", orderId,
            "productId", "P001",
            "quantity", 1
        ));
    }

    public void onStockReservedSuccess(String orderId) {
        var saga = sagaRepo.findByOrderId(orderId)
                .orElseThrow(() -> new RuntimeException("Saga not found"));

        if (!"IN_PROGRESS".equals(saga.getStatus())) return;

        saga.setCurrentStep("CREATE_ORDER");
        sagaRepo.save(saga);

        kafkaTemplate.send("order-topic", Map.of(
            "action", "create",
            "orderId", orderId,
            "totalAmount", 99.99
        ));
    }

    public void onOrderCreatedSuccess(String orderId) {
        var saga = sagaRepo.findByOrderId(orderId)
                .orElseThrow(() -> new RuntimeException("Saga not found"));

        if (!"IN_PROGRESS".equals(saga.getStatus())) return;

        saga.setCurrentStep("PAYMENT_INITIATED");
        sagaRepo.save(saga);

        kafkaTemplate.send("payment-topic", Map.of(
            "action", "initiate",
            "orderId", orderId,
            "amount", 99.99
        ));
    }

    public void onPaymentFailed(String orderId, String reason) {
        var saga = sagaRepo.findByOrderId(orderId)
                .orElseThrow(() -> new RuntimeException("Saga not found"));

        if ("FAILED".equals(saga.getStatus()) || "COMPENSATING".equals(saga.getStatus()))
            return;

        saga.setStatus("COMPENSATING");
        sagaRepo.save(saga);

        // 触发补偿流程:先回滚订单,再释放库存
        kafkaTemplate.send("order-topic", Map.of(
            "action", "rollback",
            "orderId", orderId
        ));

        kafkaTemplate.send("stock-topic", Map.of(
            "action", "release",
            "orderId", orderId
        ));
    }

    public void onCompensationCompleted(String orderId) {
        var saga = sagaRepo.findByOrderId(orderId)
                .orElseThrow(() -> new RuntimeException("Saga not found"));
        saga.setStatus("FAILED");
        saga.setEndTime(LocalDateTime.now());
        sagaRepo.save(saga);
    }
}

1.4.4 各服务监听并响应事件

库存服务(StockService.java)
@Component
@KafkaListener(topics = "stock-topic", groupId = "stock-group")
public class StockService {

    @Autowired
    private StockRepository stockRepo;

    @Transactional
    public void handleStockEvent(Map<String, Object> event) {
        String action = (String) event.get("action");
        String orderId = (String) event.get("orderId");

        if ("reserve".equals(action)) {
            var stock = stockRepo.findById("P001").orElseThrow();
            if (stock.getQuantity() < 1) {
                throw new RuntimeException("Insufficient stock");
            }
            stock.setQuantity(stock.getQuantity() - 1);
            stockRepo.save(stock);
            // 发送成功事件
            kafkaTemplate.send("stock-event", Map.of("status", "success", "orderId", orderId));
        } else if ("release".equals(action)) {
            var stock = stockRepo.findById("P001").orElseThrow();
            stock.setQuantity(stock.getQuantity() + 1);
            stockRepo.save(stock);
            kafkaTemplate.send("stock-event", Map.of("status", "released", "orderId", orderId));
        }
    }
}
订单服务(OrderService.java)
@Component
@KafkaListener(topics = "order-topic", groupId = "order-group")
public class OrderService {

    @Autowired
    private OrderRepository orderRepo;

    @Transactional
    public void handleOrderEvent(Map<String, Object> event) {
        String action = (String) event.get("action");
        String orderId = (String) event.get("orderId");

        if ("create".equals(action)) {
            var order = new Order();
            order.setId(orderId);
            order.setTotal(99.99);
            order.setStatus("CREATED");
            orderRepo.save(order);
            kafkaTemplate.send("order-event", Map.of("status", "created", "orderId", orderId));
        } else if ("rollback".equals(action)) {
            orderRepo.deleteById(orderId);
            kafkaTemplate.send("order-event", Map.of("status", "rolled_back", "orderId", orderId));
        }
    }
}

🔍 注意:所有服务均需确保操作具备幂等性,可通过唯一键(如 orderId)判断是否已处理。

1.5 Saga 模式的优缺点分析

优点 缺点
✅ 不依赖数据库锁,适合高并发场景 ❌ 补偿逻辑复杂,易出错
✅ 支持长事务,无超时限制 ❌ 需要额外开发补偿机制
✅ 服务间松耦合,易于扩展 ❌ 故障恢复能力依赖状态持久化
✅ 易于集成事件总线(Kafka/RabbitMQ) ❌ 调试困难,链路追踪复杂

二、TCC 模式:两阶段提交的改进版

2.1 基本概念与核心思想

TCC 模式(Try-Confirm-Cancel)是一种基于“预留资源 + 提交确认 + 取消释放”的分布式事务模型,最早由 eBay 提出,后被广泛应用于金融、电商等领域。

TCC 的三个阶段如下:

  1. Try(尝试):预留资源,检查业务合法性,但不真正修改数据;
  2. Confirm(确认):正式执行业务操作,完成数据变更;
  3. Cancel(取消):释放 Try 阶段预留的资源。

关键特征:

  • Try 阶段不可靠,但必须是幂等的;
  • Confirm 和 Cancel 必须是幂等的;
  • 事务协调器(Transaction Manager)负责管理全局事务状态。

2.2 工作流程示例

仍以“下单”为例:

[开始]
   ↓
库存服务:Try → 锁定库存(冻结数量)
   ↓
订单服务:Try → 创建订单草稿(标记为待支付)
   ↓
支付服务:Try → 冻结支付金额
   ↓
全部 Try 成功 → 进入 Confirm 阶段
   ↓
库存服务:Confirm → 扣减库存
   ↓
订单服务:Confirm → 更新订单状态为已支付
   ↓
支付服务:Confirm → 完成支付
   ↓
[成功]
   ↓ 否
   ↓
任一 Try 失败 → 执行 Cancel
   ↓
库存服务:Cancel → 解冻库存
   ↓
订单服务:Cancel → 删除草稿
   ↓
支付服务:Cancel → 释放冻结金额

2.3 TCC 服务接口定义

每个服务需提供三个方法:

public interface TccService {
    boolean tryOperation(TccContext context); // 尝试阶段
    boolean confirmOperation(TccContext context); // 确认阶段
    boolean cancelOperation(TccContext context); // 取消阶段
}

2.4 实现代码示例(基于 Seata 框架)

Seata 是目前最成熟的 TCC 实现框架之一,支持 AT、TCC、XA 模式。以下使用 Seata 的 TCC 模式实现。

2.4.1 引入 Seata 依赖(pom.xml)

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2021.0.5.0</version>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.7.0</version>
</dependency>

2.4.2 配置文件(application.yml)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root

seata:
  enabled: true
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
  config:
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace: public
      group: SEATA_GROUP

2.4.3 数据库表结构

-- 事务日志表(Seata 自动管理)
CREATE TABLE global_table (
    xid VARCHAR(128) NOT NULL PRIMARY KEY,
    transaction_id BIGINT,
    status TINYINT NOT NULL,
    application_id VARCHAR(32),
    transaction_service_group VARCHAR(32),
    -- 其他字段...
);

-- 分支事务表
CREATE TABLE branch_table (
    branch_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    xid VARCHAR(128) NOT NULL,
    transaction_id BIGINT NOT NULL,
    resource_group_id VARCHAR(32),
    resource_id VARCHAR(256),
    lock_key VARCHAR(128),
    branch_type VARCHAR(8),
    status TINYINT,
    client_id VARCHAR(64),
    application_data VARCHAR(2000),
    gmt_create DATETIME,
    gmt_modified DATETIME,
    INDEX idx_xid (xid),
    INDEX idx_transaction_id (transaction_id)
);

2.4.4 TCC 服务实现

库存服务(StockTccService.java)
@Service
@Tcc
public class StockTccService {

    @Autowired
    private StockMapper stockMapper;

    @TccMethod
    public boolean tryReserveStock(TccContext context) {
        String orderId = context.getOrderId();
        Integer productId = context.getProductId();
        Integer quantity = context.getQuantity();

        // 查询库存
        StockEntity stock = stockMapper.selectByProductId(productId);
        if (stock == null || stock.getAvailable() < quantity) {
            return false; // 无法预留
        }

        // 冻结库存
        stock.setAvailable(stock.getAvailable() - quantity);
        stock.setFrozen(stock.getFrozen() + quantity);
        stockMapper.updateById(stock);

        // 记录预留信息(可选)
        reserveLogMapper.insert(new ReserveLog(orderId, productId, quantity, "TRY"));

        return true;
    }

    @TccMethod
    public boolean confirmReserveStock(TccContext context) {
        String orderId = context.getOrderId();
        Integer productId = context.getProductId();
        Integer quantity = context.getQuantity();

        StockEntity stock = stockMapper.selectByProductId(productId);
        stock.setAvailable(stock.getAvailable() - quantity);
        stock.setFrozen(stock.getFrozen() - quantity);
        stockMapper.updateById(stock);

        reserveLogMapper.updateStatus(orderId, "CONFIRMED");

        return true;
    }

    @TccMethod
    public boolean cancelReserveStock(TccContext context) {
        String orderId = context.getOrderId();
        Integer productId = context.getProductId();
        Integer quantity = context.getQuantity();

        StockEntity stock = stockMapper.selectByProductId(productId);
        stock.setAvailable(stock.getAvailable() + quantity);
        stock.setFrozen(stock.getFrozen() - quantity);
        stockMapper.updateById(stock);

        reserveLogMapper.updateStatus(orderId, "CANCELLED");

        return true;
    }
}
订单服务(OrderTccService.java)
@Service
@Tcc
public class OrderTccService {

    @Autowired
    private OrderMapper orderMapper;

    @TccMethod
    public boolean tryCreateOrder(TccContext context) {
        String orderId = context.getOrderId();
        Double amount = context.getAmount();

        OrderEntity order = new OrderEntity();
        order.setId(orderId);
        order.setAmount(amount);
        order.setStatus("DRAFT"); // 草稿状态
        order.setCreateTime(LocalDateTime.now());

        int result = orderMapper.insert(order);
        return result > 0;
    }

    @TccMethod
    public boolean confirmCreateOrder(TccContext context) {
        String orderId = context.getOrderId();
        OrderEntity order = orderMapper.selectById(orderId);
        order.setStatus("PAID");
        order.setUpdateTime(LocalDateTime.now());
        int result = orderMapper.updateById(order);
        return result > 0;
    }

    @TccMethod
    public boolean cancelCreateOrder(TccContext context) {
        String orderId = context.getOrderId();
        orderMapper.deleteById(orderId);
        return true;
    }
}
支付服务(PaymentTccService.java)
@Service
@Tcc
public class PaymentTccService {

    @Autowired
    private PaymentMapper paymentMapper;

    @TccMethod
    public boolean tryPay(TccContext context) {
        String orderId = context.getOrderId();
        Double amount = context.getAmount();

        PaymentEntity payment = new PaymentEntity();
        payment.setOrderId(orderId);
        payment.setAmount(amount);
        payment.setStatus("FROZEN");
        payment.setCreateTime(LocalDateTime.now());

        int result = paymentMapper.insert(payment);
        return result > 0;
    }

    @TccMethod
    public boolean confirmPay(TccContext context) {
        String orderId = context.getOrderId();
        PaymentEntity payment = paymentMapper.selectByOrderId(orderId);
        payment.setStatus("SUCCESS");
        payment.setUpdateTime(LocalDateTime.now());
        int result = paymentMapper.updateById(payment);
        return result > 0;
    }

    @TccMethod
    public boolean cancelPay(TccContext context) {
        String orderId = context.getOrderId();
        paymentMapper.deleteByOrderId(orderId);
        return true;
    }
}

2.4.5 主业务调用入口(OrderController.java)

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private StockTccService stockTccService;

    @Autowired
    private OrderTccService orderTccService;

    @Autowired
    private PaymentTccService paymentTccService;

    @PostMapping("/create")
    public ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {
        TccContext context = new TccContext();
        context.setOrderId(request.getOrderId());
        context.setProductId(request.getProductId());
        context.setQuantity(request.getQuantity());
        context.setAmount(request.getAmount());

        try {
            boolean try1 = stockTccService.tryReserveStock(context);
            boolean try2 = orderTccService.tryCreateOrder(context);
            boolean try3 = paymentTccService.tryPay(context);

            if (!try1 || !try2 || !try3) {
                throw new RuntimeException("One or more try operations failed");
            }

            // 所有 try 成功,进入 confirm
            boolean confirm1 = stockTccService.confirmReserveStock(context);
            boolean confirm2 = orderTccService.confirmCreateOrder(context);
            boolean confirm3 = paymentTccService.confirmPay(context);

            if (!confirm1 || !confirm2 || !confirm3) {
                throw new RuntimeException("One or more confirm operations failed");
            }

            return ResponseEntity.ok("Order created successfully");
        } catch (Exception e) {
            // 若发生异常,Seata 会自动触发 cancel
            return ResponseEntity.status(500).body("Order creation failed: " + e.getMessage());
        }
    }
}

2.4.6 TCC 模式的优缺点分析

优点 缺点
✅ 严格保证事务一致性 ❌ 开发成本高,需手动编写 Try/Confirm/Cancel
✅ 适用于强一致性需求场景(如金融) ❌ 对业务侵入性强,需改造现有逻辑
✅ 性能优于 2PC,无锁等待 ❌ 无法处理网络分区下的脑裂问题
✅ Seata 等框架成熟,支持良好 ❌ 调试困难,日志分散

三、Saga vs TCC:全面对比分析

维度 Saga 模式 TCC 模式
一致性级别 最终一致性 强一致性
事务粒度 长事务,跨服务 精细控制,分阶段
开发复杂度 中等(需设计补偿) 高(需实现三阶段)
性能表现 高(异步补偿) 中高(同步调用)
容错能力 强(状态持久化) 一般(依赖协调器)
适用场景 电商、物流、审批流程 金融、支付、账户转账
框架支持 Kafka + 自研 / Axon Seata / ByteTCC
调试难度 高(链路长) 中(有日志跟踪)
幂等要求 必须 必须

四、技术选型建议与最佳实践

4.1 如何选择?——根据业务场景决定

业务类型 推荐模式 理由
电商平台下单 ✅ Saga 长流程、容忍短暂不一致
金融交易、余额变动 ✅ TCC 强一致性要求
订单审批流 ✅ Saga 多级审批,补偿容易
跨银行转账 ✅ TCC 必须保证原子性
日志收集、通知推送 ✅ Saga 最终一致即可

4.2 最佳实践指南

✅ Saga 模式最佳实践

  1. 使用 Kafka 或 RabbitMQ 实现事件驱动;
  2. 所有服务操作必须 幂等
  3. 使用数据库或 Redis 持久化 Saga 状态;
  4. 添加 超时机制,防止长期挂起;
  5. 使用 链路追踪(如 SkyWalking)监控流程;
  6. 补偿操作尽量 异步执行,避免阻塞。

✅ TCC 模式最佳实践

  1. 使用 Seata 框架减少编码负担;
  2. Try 阶段只做资源锁定,不做业务变更;
  3. Confirm 和 Cancel 必须 幂等
  4. 设置合理的 超时时间(如 30s);
  5. 监控分支事务状态,及时发现悬挂事务;
  6. 在生产环境开启 事务日志审计

五、总结与展望

在微服务架构中,分布式事务并非“非黑即白”,而是需要根据具体业务需求权衡取舍。Saga 模式以其轻量、灵活、高可用的特点,成为处理长事务的理想选择;而 TCC 模式凭借其强一致性保障,更适合对数据一致性要求极高的领域。

未来趋势:

  • 更多企业将采用 混合模式:关键路径用 TCC,非关键路径用 Saga;
  • AI 辅助生成补偿逻辑;
  • 区块链+分布式事务协同方案探索;
  • 云原生平台内置分布式事务治理能力。

🎯 结论:没有绝对最优的方案,只有最适合业务的架构。理解原理、评估代价、持续演进,才是通往稳定可靠的微服务之路。

💡 附录:推荐工具与学习资源

本文撰写于 2025 年 4 月,基于实际项目经验整理,内容仅供参考。

相似文章

    评论 (0)