引言

在微服务架构中,每个服务拥有独立的数据库,跨服务的业务操作涉及多个数据库,传统的ACID事务(如2PC两阶段提交)因强一致性和阻塞问题,几乎不再适用。Saga模式是微服务中解决分布式事务的主流方案,它通过本地事务串接与补偿操作,在保证最终一致性的同时维持系统的高可用性。

本文将深入剖析Saga的两种实现模式——编排模式(Choreography)和协调模式(Orchestration),给出完整的代码示例,并分享生产环境中的设计要点与陷阱规避。

什么是Saga?

Saga最初由Hector Garcia-Molina和Kenneth Salem在1987年的论文《Sagas》中提出。一个Saga是一系列本地事务(T1, T2, …, Tn)的序列,每个本地事务都会更新数据库并发布事件或消息触发下一个事务。如果某个本地事务(Ti)失败,Saga将执行补偿事务(Ci, Ci-1, …, C1)来回滚之前的所有正面操作。

核心特点:

  • 每个Ti都是可独立提交的本地ACID事务。
  • Ci是Ti的补偿操作,必须是幂等的、可逆的。
  • 不保证隔离性(中间状态对外可见),需要业务层处理。

编排模式(Choreography-based Saga)

在编排模式中,没有中央协调器。每个服务监听上游事件,完成自己的本地事务后发布下游事件,像一支无声的舞蹈,每个舞者听音乐自行起舞。

流程示例:电商下单

  1. 订单服务创建订单(状态=待处理)→ 发布 OrderCreated 事件。
  2. 库存服务监听 OrderCreated,预扣库存 → 发布 StockReserved 事件。
  3. 支付服务监听 StockReserved,发起扣款 → 发布 PaymentCompleted 事件。
  4. 订单服务监听 PaymentCompleted,将订单状态改为"已确认"。

失败补偿:如果支付失败,支付服务发布 PaymentFailed 事件,库存服务监听后释放库存,订单服务监听后取消订单。

编排模式代码示例

// 订单服务 — 创建订单并发布事件
@Service
public class OrderService {
    @Transactional
    public void createOrder(OrderRequest req) {
        Order order = orderRepository.save(new Order(req, OrderStatus.PENDING));
        // 发布事件到消息队列
        eventPublisher.publish(new OrderCreatedEvent(order.getId(), req.getItems()));
    }
}

// 库存服务 — 监听订单事件并预扣库存
@Component
public class InventoryService {
    @EventListener
    @Transactional
    public void onOrderCreated(OrderCreatedEvent event) {
        try {
            inventoryRepository.reserveStock(event.getItems());
            eventPublisher.publish(new StockReservedEvent(event.getOrderId()));
        } catch (InsufficientStockException e) {
            eventPublisher.publish(new StockReservationFailedEvent(event.getOrderId()));
        }
    }

    // 补偿操作:释放库存
    @EventListener
    @Transactional
    public void onPaymentFailed(PaymentFailedEvent event) {
        inventoryRepository.releaseStock(event.getOrderId());
    }
}

优缺点

  • 优点:去中心化、无单点故障,适合简单流程;开发量小,新增参与者只需添加事件监听。
  • 缺点:流程逻辑分散在多个服务中,难以追踪整体走向;随着步骤增多,事件依赖网会变得复杂;测试时需要同时启动多个服务。

协调模式(Orchestration-based Saga)

在协调模式中,引入一个中央协调器(Saga Orchestrator),该协调器负责按顺序调用各服务,并决定何时执行补偿操作。协调器如同指挥家,每个乐手只对指挥家负责。

流程示例:同一电商下单

  1. Saga协调器调用 订单服务:创建订单。
  2. 协调器调用 库存服务:预扣库存。
  3. 协调器调用 支付服务:执行扣款。
  4. 所有步骤成功 → Saga完成。
  5. 若步骤3失败 → 协调器调用 库存服务(释放库存)→ 调用 订单服务(取消订单)。

协调模式代码示例(基于 Camunda / 开源轴框)

// Saga 协调器 — 流程定义
public class CreateOrderSaga {
    
    private final OrderServiceProxy orderService;
    private final InventoryServiceProxy inventoryService;
    private final PaymentServiceProxy paymentService;
    
    public void execute(OrderRequest request) {
        SagaDefinition saga = SagaBuilder.saga()
            .step()
                .invoke(orderService::createOrder)
                .withCompensation(orderService::cancelOrder)
            .step()
                .invoke(inventoryService::reserveStock)
                .withCompensation(inventoryService::releaseStock)
            .step()
                .invoke(paymentService::processPayment)
                .withCompensation(paymentService::refund)
            .build();
        
        saga.execute(request);
    }
}

// 使用开源框架的注解方式(伪代码)
@Compensable(compensationMethod = "cancelOrder")
public OrderResult createOrder(OrderRequest req) {
    return orderRepository.save(new Order(req));
}

public void cancelOrder(OrderRequest req) {
    orderRepository.deleteByRequestId(req.getRequestId());
}

优缺点

  • 优点:流程逻辑集中管理,便于监控和调试;协调器可在失败时使用不同策略。
  • 缺点:协调器是单点(需要高可用实现);协调器需了解所有服务的API,出现了逻辑集中化的风险。

两种模式的深度对比

维度编排模式协调模式
控制流去中心化,事件驱动中心化,命令式驱动
逻辑位置分散于各服务集中于Saga协调器
新增步骤添加事件监听即可修改协调器流程定义
故障排查难:分布式追踪必需易:集中状态监控
事务状态隐式(无全局状态)显式(协调器维护状态)
循环依赖风险可能出现事件死循环协调器统一编排,无此风险
性能开销低(事件异步传递)高(协调器等待同步调用)
适用场景≦5步、简单流程、独立团队>5步、复杂流程、集中管理

生产环境关键设计要点

1. 幂等性设计

Saga在重试或补偿触发时可能多次执行同一操作,所有操作必须幂等。例如:

-- 使用唯一业务键防止重复扣款
UPDATE balance SET amount = amount - :amt
WHERE user_id = :uid AND request_id = :reqId AND status = 'PENDING';

2. 补偿操作的"语义可逆"

并非所有操作都能完美补偿。例如"发送邮件"不可逆,此时应采用确务设计(Idempotent Receiver),在发送前记录意图,实际发送时去重。

  • 可逆补偿:库存释放(数学上可逆)
  • 不可逆操作:短信通知 → 采用"撤销窗口"或业务对账补偿

3. 状态记录与可观测性

建议维护 saga_instance 表记录每个Saga的当前状态、已完成的步骤、补偿进度,并按照OpenTelemetry标准记录Trace ID。

4. 长事务与用户体感

Saga的每个本地事务是即时提交的,但整个Saga的完成可能需要数秒甚至数分钟。建议:

  • 使用"预创建"模式:订单先创建为"处理中",Saga完成后确认
  • 前端通过WebSocket或轮询获取最终状态
  • 超时告警:超过阈值自动触发人工干预

5. 选择合适的消息中间件

推荐使用支持"事务消息"或"消息表方案"的中间件,确保事件一定送达。例如RocketMQ的事务消息、Kafka的幂等生产者。

开源框架推荐

  • Seata:阿里巴巴开源,支持Saga、TCC、AT等多种模式,Spring友好。
  • Camunda:基于BPMN2.0的轻量级工作流引擎,可用JSON定义Saga流程。
  • Eventuate Tram Sagas:Gregory Young专为微场景设计的Java库,支持编排与协调双模式。
  • Axon Framework:CQRS+Event Sourcing框架,内置Saga支持。

总结

Saga不是一种具体的框架,而是一种架构模式。选择编排模式还是协调模式,取决于团队的微服务成熟度、流程的复杂度以及对集中管控的接受程度。记住:

  • 编排模式 = "去中心化通信",适合小而美的服务网格。
  • 协调模式 = "集中式协调",适合流程严谨的企业级业务。

在实际项目中,混合使用两种模式也不少见——同一系统内简单流程用编排,复杂流程用协调。关键是坚持最终一致性的心智模型,把每一次Saga都当作一次可能中途折返的长途旅行来设计。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部