mcpField
S
spring-boot-saga-pattern

spring-boot-saga-pattern

github.com/giuseppe-trisciuoglio开发3653.7k 次安装7 周前更新
代码仓库

关于 spring-boot-saga-pattern

为 Spring Boot 微服务提供 Saga 分布式事务设计指南,涵盖事件驱动的编排与集中式协调,以及补偿、消息处理和监控。

主要功能

  1. 01编舞式与编排式设计
  2. 02本地事务与补偿操作
  3. 03Kafka 和 RabbitMQ 配置指导
  4. 04Saga 状态与故障处理
  5. 05执行监控与指标

适用场景

  1. 01设计跨微服务事务
  2. 02以最终一致性替代两阶段提交
  3. 03处理服务故障后的回滚

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-saga-pattern

安装后在 Claude Code 中输入/spring-boot-saga-pattern确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
choreography-implementation.md
compensating-transactions.md
error-handling-retry.md
event-driven-architecture.md
examples.md
orchestration-implementation.md
pitfalls-solutions.md
reference.md
saga-pattern-definition.md
state-management.md
testing-strategies.md

详细介绍

Spring Boot Saga 模式

概述

使用 Saga 模式在微服务之间实现分布式事务。用一系列本地事务和补偿操作取代两阶段提交。支持基于 Kafka、RabbitMQ 或 Axon Framework 的编舞(事件驱动)和编排(集中式协调器)方式。

适用场景

  • 构建跨多个微服务的分布式事务
  • 用更具扩展性的解决方案取代两阶段提交(2PC)
  • 在服务发生故障时处理事务回滚
  • 确保微服务架构中的最终一致性
  • 为失败的操作实现补偿事务
  • 协调跨多个服务的复杂业务流程

触发短语:分布式事务、Saga 模式、补偿事务、微服务事务、最终一致性、跨服务回滚、编排模式、编舞模式

说明

1. 设计事务流程

映射操作序列及其补偿事务:

Order → Payment → Inventory → Shipment
  ↓        ↓        ↓          ↓
Cancel  Refund   Release    Cancel

验证:确认每个正向步骤都有对应的补偿操作。

2. 选择实现方式
方式适用场景技术栈
编舞全新项目、参与者较少Spring Cloud Stream + Kafka/RabbitMQ
编排复杂工作流、棕地项目Axon Framework、Eventuate Tram、Camunda

验证:选择前评估团队的专业知识和系统复杂度。

3. 使用本地事务实现服务

每个服务以原子方式完成其本地 ACID 事务:

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final KafkaTemplate<String, Object> kafka;

    @Transactional
    public Order createOrder(CreateOrderCommand cmd) {
        Order order = orderRepository.save(new Order(cmd.orderId(), cmd.items()));
        kafka.send("order.created", new OrderCreatedEvent(order.getId(), order.getItems()));
        return order;
    }
}

验证:测试本地事务是否在事件发布前提交。

4. 实现补偿事务

每个正向操作都需要一个幂等的补偿操作:

@Service
@RequiredArgsConstructor
public class PaymentService {
    private final PaymentRepository paymentRepository;
    private final KafkaTemplate<String, Object> kafka;

    public void processPayment(PaymentRequest request) {
        Payment payment = paymentRepository.save(new Payment(request.orderId(), request.amount()));
        kafka.send("payment.processed", new PaymentProcessedEvent(payment.getId(), request.orderId()));
    }

    @Transactional
    public void refundPayment(String paymentId) {
        paymentRepository.findById(paymentId)
            .ifPresent(p -> {
                p.setStatus(REFUNDED);
                paymentRepository.save(p);
                kafka.send("payment.refunded", new PaymentRefundedEvent(paymentId));
            });
    }
}

验证:确认补偿操作可以安全地执行多次(幂等性)。

5. 设置消息代理

配置 Kafka,使用幂等消费者:

@Configuration
@EnableKafka
public class KafkaConfig {
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory(
            ConsumerFactory<String, Object> consumerFactory) {
        ConcurrentKafkaListenerContainerFactory<String, Object> factory =
            new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory);
        factory.setCommonErrorHandler(new DefaultErrorHandler());
        return factory;
    }
}

验证:启用事务 ID,并验证恰好一次语义。

6. 实现 Saga 编排器(仅限编排方式)
@Service
@RequiredArgsConstructor
public class OrderSagaOrchestrator {
    private final KafkaTemplate<String, Object> kafka;
    private final SagaStateRepository sagaStateRepo;

    public void startSaga(OrderRequest request) {
        String sagaId = UUID.randomUUID().toString();
        sagaStateRepo.save(new SagaState(sagaId, STARTED, LocalDateTime.now()));
        kafka.send("saga.order.start", new StartOrderSagaCommand(sagaId, request));
    }

    @KafkaListener(topics = "payment.failed")
    public void handlePaymentFailed(PaymentFailedEvent event) {
        kafka.send("order.compensate", new CompensateOrderCommand(event.getSagaId()));
        kafka.send("inventory.compensate", new ReleaseInventoryCommand(event.getSagaId()));
        sagaStateRepo.updateStatus(event.getSagaId(), FAILED);
    }
}

验证:确认在发送命令前 Saga 状态已持久化。检查每条失败路径是否都会触发补偿。

7. 实现事件处理器(仅限编舞方式)
@Service
public class OrderEventHandler {
    private final OrderService orderService;
    private final KafkaTemplate<String, Object> kafka;

    @KafkaListener(topics = "payment.processed", groupId = "order-service")
    public void onPaymentProcessed(PaymentProcessedEvent event) {
        try {
            InventoryReservedEvent result = orderService.reserveInventory(event.toInventoryRequest());
            kafka.send("inventory.reserved", result);
        } catch (InsufficientInventoryException e) {
            kafka.send("inventory.insufficient", new InsufficientInventoryEvent(event.getOrderId(), event.getPaymentId()));
        }
    }
}

验证:测试每个事件处理器是否能正确触发下一步或补偿操作。

8. 添加监控和可观测性
@Configuration
public class SagaMetricsConfig {
    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

跟踪:Saga 执行时长、补偿次数、失败率、卡住的 Saga。

验证:为超出预期时长的 Saga 设置告警。

最佳实践

设计:

  • 使用数据库约束或去重表,使补偿事务保持幂等
  • 使用不可变事件(Java record),防止意外修改
  • 将 Saga 状态存储在持久化存储中,以便恢复

错误处理:

  • 为服务间调用实现断路器
  • 对超出重试限制的消息使用死信队列
  • 为每个 Saga 步骤设置适当的超时(默认 30s,可配置)

监控:

  • 跟踪 Saga 状态:PENDING、COMPLETED、COMPENSATING、FAILED
  • 监控补偿操作的执行时间
  • 当 Saga 超出 SLA 时长时发出告警

限制和警告

  • 每个正向事务都必须有对应的补偿事务
  • 补偿事务必须具备幂等性,以应对重试场景
  • Saga 状态必须持久化,以应对故障并支持恢复
  • Saga 参与者之间不得使用同步通信
  • Saga 提供的是最终一致性,而非强一致性
  • 测试所有故障场景,包括部分故障
  • 对于复杂的编排,考虑使用 Axon Framework 或 Eventuate
  • 确保消息代理具备高可用性

示例

基于编舞的 Saga
// Application.java
@SpringBootApplication
@EnableKafka
@EnableKafkaListeners
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

// Event Classes (immutable)
public record OrderCreatedEvent(String orderId, List<OrderItem> items) {}
public record PaymentProcessedEvent(String paymentId, String orderId) {}
public record InventoryReservedEvent(String reservationId, String orderId) {}
public record PaymentFailedEvent(String orderId, String reason) {}
public record InsufficientInventoryEvent(String orderId, String paymentId) {}

// OrderService with compensation
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final KafkaTemplate<String, Object> kafka;

    @KafkaListener(topics = "payment.failed", groupId = "order-service")
    public void handleCompensation(PaymentFailedEvent event) {
        orderRepository.findByOrderId(event.orderId())
            .ifPresent(order -> {
                order.setStatus(CANCELLED);
                orderRepository.save(order);
            });
    }
}
使用 Axon Framework 的基于编排的 Saga
// Command
@Aggregate
public class OrderAggregate {
    @AggregateIdentifier
    private String orderId;

    @CommandHandler
    public OrderAggregate(CreateOrderCommand cmd) {
        apply(new OrderCreatedEvent(cmd.orderId(), cmd.items()));
    }

    @EventSourcingHandler
    public void on(OrderCreatedEvent event) {
        this.orderId = event.orderId();
    }

    @CommandHandler
    public void handle(CancelOrderCommand cmd) {
        apply(new OrderCancelledEvent(cmd.orderId(), cmd.reason()));
    }
}

参考资料

评论

支持 Markdown