编排式 Saga 模式

编排式 Saga 模式(Orchestrated Saga)是指由一个中央协调者(Orchestrator)控制多个服务间的事务执行。与协作式 Saga 模式不同,编排式 Saga 模式不依赖于事件驱动,而是通过协调者来控制整个 Saga 流程的执行。协调者负责调用各个参与服务,确保每个子事务按顺序执行,并在某个子事务失败时触发补偿操作。

编排式 Saga 模式实现步骤

我们将实现一个基于编排式 Saga 模式的跨服务数据保存方案,使用 Spring Boot 来开发微服务,使用 RabbitMQ 或 Kafka 作为消息队列进行通信,并通过一个中央协调者来管理整个 Saga 流程。

以下是如何使用编排式 Saga 模式来实现一个典型的跨服务操作:订单创建和库存扣减。

1. 架构设计

我们有两个微服务:

  • Order Service:负责创建订单。
  • Inventory Service:负责管理库存。
  • Saga Orchestrator Service:协调整个 Saga 流程,包括执行各服务事务并在失败时触发补偿操作。

2. 技术栈

  • Spring Boot:用于开发微服务。
  • Spring Cloud:用于服务注册、发现和治理。
  • Spring AMQP / Kafka:用于服务间消息传递(可选择 RabbitMQ 或 Kafka)。
  • Spring Data JPA:用于数据库操作。
  • Transactional Outbox Pattern:用来确保跨服务操作的一致性。

3. 系统流程

  1. Order Service:接收创建订单请求,调用 Saga Orchestrator Service 开始 Saga 流程。
  2. Saga Orchestrator:协调 Inventory Service 扣减库存,等到确认成功后,继续后续操作(如创建订单)。
  3. Inventory Service:接收扣减库存请求,执行库存扣减,如果成功,通知 Saga Orchestrator。如果失败,则触发补偿操作。
  4. 补偿操作:如果任何一个服务的事务失败,Saga Orchestrator 会调用补偿操作回滚之前的事务,确保最终一致性。

4. Spring Boot 示例实现

4.1 创建 Order Service

Order Service 负责处理订单请求,并与 Saga Orchestrator 配合,触发 Saga 流程。

java 复制代码
// OrderService.java
@Service
public class OrderService {

    @Autowired
    private SagaOrchestrator sagaOrchestrator;

    // 创建订单
    @Transactional
    public void createOrder(Order order) {
        // Step 1: 创建订单
        orderRepository.save(order);

        // Step 2: 调用 Saga Orchestrator 开始整个流程
        sagaOrchestrator.startSaga(order);
    }
}

4.2 创建 Saga Orchestrator Service

Saga Orchestrator Service 是整个 Saga 模式的核心,它负责协调各个服务之间的事务执行。首先,它会启动 Saga 事务,接着协调 Inventory Service 执行库存扣减操作,并处理补偿操作。

java 复制代码
// SagaOrchestrator.java
@Service
public class SagaOrchestrator {

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private OrderRepository orderRepository;

    // 启动 Saga 流程
    @Transactional
    public void startSaga(Order order) {
        try {
            // Step 1: 调用库存服务扣减库存
            boolean inventorySuccess = inventoryService.decreaseInventory(order.getItemId(), order.getQuantity());
            if (!inventorySuccess) {
                throw new Exception("Inventory insufficient");
            }

            // Step 2: 库存扣减成功后,继续创建订单
            order.setStatus("Created");
            orderRepository.save(order);

        } catch (Exception e) {
            // Step 3: 如果出错,执行补偿操作
            compensate(order);
        }
    }

    // 补偿方法,回滚库存操作
    private void compensate(Order order) {
        // 回滚库存,增加库存
        inventoryService.rollbackInventory(order.getItemId(), order.getQuantity());

        // 回滚订单,设置订单为失败状态
        order.setStatus("Failed");
        orderRepository.save(order);
    }
}

4.3 创建 Inventory Service

Inventory Service 负责扣减库存并通知 Saga Orchestrator 执行后续操作。

java 复制代码
// InventoryService.java
@Service
public class InventoryService {

    @Autowired
    private InventoryRepository inventoryRepository;

    // 扣减库存
    @Transactional
    public boolean decreaseInventory(Long itemId, int quantity) {
        Inventory inventory = inventoryRepository.findByItemId(itemId);
        if (inventory.getStock() < quantity) {
            return false; // 库存不足
        }
        inventory.setStock(inventory.getStock() - quantity);
        inventoryRepository.save(inventory);
        return true; // 库存扣减成功
    }

    // 补偿操作,回滚库存
    @Transactional
    public void rollbackInventory(Long itemId, int quantity) {
        Inventory inventory = inventoryRepository.findByItemId(itemId);
        inventory.setStock(inventory.getStock() + quantity); // 恢复库存
        inventoryRepository.save(inventory);
    }
}

4.4 消息队列(RabbitMQ 或 Kafka)集成

为了实现 Saga 模式的跨服务通信,我们可以使用消息队列来传递消息。这里我们使用 RabbitMQ 作为消息队列。

在 application.properties 中配置 RabbitMQ:

java 复制代码
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

在 SagaOrchestrator 和 InventoryService 中,我们可以通过 RabbitTemplate 来发送和接收消息。

java 复制代码
// SagaOrchestrator.java
@Autowired
private RabbitTemplate rabbitTemplate;

// 启动 Saga 流程时,发送事件
public void startSaga(Order order) {
    // 发送一个消息,通知库存服务处理库存
    rabbitTemplate.convertAndSend("inventoryExchange", "inventory.decrease", order);
}

// 监听库存扣减消息的回调
@RabbitListener(queues = "inventory.decrease.queue")
public void handleInventoryDecrease(Order order) {
    try {
        // 扣减库存并继续订单处理
        boolean inventorySuccess = inventoryService.decreaseInventory(order.getItemId(), order.getQuantity());
        if (!inventorySuccess) {
            throw new Exception("Inventory insufficient");
        }

        // 订单处理继续
        order.setStatus("Created");
        orderRepository.save(order);
    } catch (Exception e) {
        // 执行补偿操作
        compensate(order);
    }
}

4.5 设置消息队列的交换机和队列

java 复制代码
@Configuration
public class RabbitMQConfig {

    @Bean
    public TopicExchange inventoryExchange() {
        return new TopicExchange("inventoryExchange");
    }

    @Bean
    public Queue inventoryDecreaseQueue() {
        return new Queue("inventory.decrease.queue");
    }

    @Bean
    public Binding inventoryDecreaseBinding() {
        return BindingBuilder.bind(inventoryDecreaseQueue()).to(inventoryExchange()).with("inventory.decrease");
    }
    
    @Bean
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
                                         Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
        return rabbitTemplate;
    }
}

5. 确保最终一致性

在编排式 Saga 模式中,每个服务通过本地事务来保证操作的原子性,并通过协调者来确保每个子事务执行成功。当某个服务失败时,协调者会触发补偿操作回滚之前的操作。关键要素是:

补偿操作:服务必须提供回滚或补偿机制,确保在失败时能够撤销已完成的事务。

幂等性:补偿操作应该是幂等的,确保多次执行不会产生不一致的结果。

6. 总结

编排式 Saga 模式通过中央协调者来管理跨服务事务,确保最终一致性和数据可靠性。使用 RabbitMQ 或 Kafka 进行服务间的消息通信,可以将系统解耦,提高扩展性。在这种模式下,协调者充当了服务之间的桥梁,负责事务流的管理,并在必要时执行补偿操作。

相关推荐
m0_748247802 分钟前
【Spring Boot】统一数据返回
java·spring boot·后端
星迹日1 小时前
数据结构:包装类和泛型
java·开发语言·数据结构·笔记·泛型·通配符·包装类
重生之绝世牛码3 小时前
Java设计模式 —— 【行为型模式】命令模式(Command Pattern) 详解
java·大数据·开发语言·设计模式·命令模式·设计原则
荆州克莱5 小时前
redis学习笔记(一)了解redis
spring boot·spring·spring cloud·css3·技术
杨荧5 小时前
【开源免费】基于Vue和SpringBoot的贸易行业crm系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源
java排坑日记6 小时前
poi-tl+kkviewfile实现生成pdf业务报告
java·pdf·word
V+zmm101346 小时前
校园约拍微信小程序设计与实现ssm+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
猿来入此小猿6 小时前
基于SpringBoot小说平台系统功能实现四
java·spring boot·毕业设计·毕业源码·在线小说阅读·在线小说平台·免费学习:猿来入此
Cosmoshhhyyy7 小时前
LeetCode:2274. 不含特殊楼层的最大连续楼层数(排序 Java)
java·算法·leetcode
Dolphin_Home7 小时前
Spring Boot 多环境配置与切换
java·spring boot·后端