【Spring Boot】微服务架构下Saga模式的实战解析

现代微服务虽小巧、自治且能独立部署------但这恰恰埋下了一个大问题。

想想那些典型的药房处方管理、支付、银行、物流、供应链或预订系统吧,它们的操作往往横跨多个微服务

  • 创建订单
  • 预留库存
  • 处理支付
  • 创建发货单
  • 更新积分

每个服务都运行在各自独立的数据库服务网络边界内。

如果某个操作半路掉链子,整个业务流程绝不能把系统丢在一个不一致的状态里不管。

这时候,Saga 模式就变得至关重要了。

Saga 是什么?为什么不用 ACID 事务?

一个 Saga 就是一串本地事务的集合。每个本地事务更新单个服务内的数据,并发布一个事件来触发下一步。

一旦发生失败:

  • Saga 就会启动补偿操作,回滚前面已经完成的步骤。

为什么 ACID(两阶段提交,2PC)在微服务里玩不转?

问题 解释
2PC 是阻塞的 要是协调者崩溃,整个系统就卡住了。
微服务各有独立的数据库 分布式数据库之间没有共享的提交日志。
扩展性差 锁会蔓延到整个分布式系统。
云服务普遍不支持 XA 事务 DynamoDB, S3, Mongo, Kafka, RDS 等等都不支持。

Saga 通过事件驱动或集中编排的补偿逻辑,巧妙地避开了这些问题。

Saga 架构图

1. 实战用例:药房处方订单处理

场景

想象一个在线药房处方平台,客户下单购买一个产品。

这个系统涉及三个独立服务:

  1. 订单服务 ------ 创建订单记录。
  2. 库存服务 ------ 预留库存中的产品。
  3. 支付服务 ------ 向客户收款。

没有 Saga 会怎样?

如果其中任何一步失败(比如支付失败),前面步骤可能导致系统状态不一致:

  • 订单已创建
  • 库存已预留
  • 支付失败
  • 结果:库存被卡住,订单不完整,需要人工干预。

这在企业系统中是绝对无法接受的,尤其是在多个微服务交互的场景下。

Saga 的解决方案

使用 Saga 编排模式 ,每个服务执行自己的本地事务 ,一旦失败,就通过补偿操作回滚前面的步骤:

  • 步骤 1: 订单服务创建订单。
  • 步骤 2: 库存服务预留库存。
  • 步骤 3: 支付服务向客户收款。
  • 失败处理: 如果支付失败,编排器会调用库存服务释放库存。

这确保了跨服务的业务级别的一致性

2. 为什么我们需要 Saga 模式?

  1. 分布式微服务: 传统的 ACID 事务无法跨越多个微服务。
  2. 最终一致性: 确保最终结果一致,无需全局资源锁。
  3. 容错性: 如果后续步骤失败,能自动回滚前面的步骤。
  4. 云原生: 能与云环境中可扩展的服务无缝协作。

松耦合:每个服务保持独立,仅通过 API 通信。

3. 使用 Saga 的好处

  • 可靠性: 补偿事务防止数据不一致。
  • 可扩展性: 每个服务可以独立扩展;编排是集中化的。
  • 韧性: 优雅地处理部分故障。
  • 业务级别的一致性: 关注正确的业务结果,而非严格的数据库一致性。
  • 监控与调试: 中央编排器为每个事务步骤提供清晰的日志。

4. 代码示例逐步解析

下面是对你的 Spring Boot Saga 项目中关键脚本的详细解读

4.1 订单模型 (Order.java)

java 复制代码
package com.example.orderservice;
public class Order {
    private Long id;
    private String product;
    private String status;
    public Order() {
        // default constructor (required for Jackson)
    }
    public Order(Long id, String product, String status) {
        this.id = id;
        this.product = product;
        this.status = status;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getProduct() {
        return product;
    }
    public void setProduct(String product) {
        this.product = product;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
}
  • 用途: 代表一个订单。
  • 业务逻辑: 存储产品名称、订单ID和状态(CREATED,CANCELLED,COMPLETED)。

4.2 订单控制器 (OrderController.java)

java 复制代码
package com.example.orderservice;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders")
public class OrderController {
    @PostMapping
    public Order create(@RequestParam("product") String product) {
        return new Order(1L, product, "CREATED");
    }
    @PostMapping("/cancel")
    public String cancel() {
        return "ORDER_CANCELLED";
    }
    @PostMapping("/complete")
    public String complete() {
        return "ORDER_COMPLETED";
    }
}
  • 端点
    • POST /orders → 创建订单。
    • POST /orders/cancel → 取消订单(用于补偿)。
    • POST /orders/complete → 标记订单完成。
  • 为何适用于 Saga: 端点简单、可预测,并返回对象或状态字符串供编排器使用。

4.3 库存控制器 (InventoryController.java)

java 复制代码
package com.example.inventoryservice;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/inventory")
public class InventoryController {
    @PostMapping("/reserve")
    public boolean reserve(@RequestParam("product") String product) {
        return !"FAIL".equals(product);
    }
    @PostMapping("/release")
    public void release(@RequestParam("product") String product) {}
}
  • 用途: 预留产品库存,如果支付失败则释放它。
  • 逻辑: 模拟成功/失败。在企业系统中,这会检查数据库中的实际库存。

4.4 支付控制器 (PaymentController.java)

java 复制代码
package com.example.paymentservice;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/payments")
public class PaymentController {
    @PostMapping("/pay")
    public boolean pay(@RequestParam("amount") double amount) {
        return amount <= 5000;
    }
    @PostMapping("/refund")
    public void refund(@RequestParam double amount) {}
}
  • 用途: 处理支付,并模拟失败以进行测试。
  • 为何适用于 Saga: 返回布尔值指示成功/失败,允许编排器触发补偿操作。

4.5 Saga 编排器 (SagaController.java)

java 复制代码
package com.example.sagaorchestrator;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/saga")
public class SagaController {
    private final RestTemplate rest = new RestTemplate();
    @PostMapping("/order")
    public String place(@RequestParam("product") String product, @RequestParam("amount") double amount) {
        Object order = rest.postForObject(
                "http://localhost:8081/orders?product=" + product,
                null,
                Object.class
        );
        
        if (order == null) {
            return "Order creation failed";
        }
        Boolean inventory = rest.postForObject(
            "http://localhost:8082/inventory/reserve?product=" + product,
            null,
            Boolean.class
        );
        if (inventory == null || !inventory) {
            return "Inventory failed";
        }
        Boolean payment = rest.postForObject(
            "http://localhost:8083/payments/pay?amount=" + amount,
            null,
            Boolean.class
        );
        if (payment == null || !payment) {
            rest.postForObject(
                    "http://localhost:8082/inventory/release?product=" + product,
                    null,
                    Void.class
            );
            return "Payment failed, compensated";
        }
        return "Order completed successfully";
    }
}
  • 流程解析
    1. 订单创建 → 第一步,必须成功才能继续。
    2. 库存预留 → 如果失败,Saga 停止。
    3. 支付处理 → 如果失败,编排器触发库存释放(补偿)。
    4. 成功 → 返回成功消息。

企业级就绪: 演示了清晰的编排、补偿和业务级别的一致性

4.6 输出验证

  1. 成功案例

  2. 支付失败案例

  3. 库存失败案例

5. 核心优势总结

  • 容错性强: 自动补偿失败的事务。
  • 业务一致性: 确保订单状态和库存始终保持一致。
  • 易于扩展: 每个服务独立运行。
  • 云原生就绪: 可在 Kubernetes 或任何云环境中运行。
  • 企业适用性强: 符合医疗、金融、数据平台等真实世界系统的需求。

6. 企业为何需要这个模式?

  • 在分布式系统中, 手动回滚容易出错。
  • Saga 确保自动恢复,减少停机时间。
  • 防止多服务工作流中出现数据不一致。
  • 支持异步处理,提升性能和可扩展性。
相关推荐
程序员泠零澪回家种桔子15 分钟前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
GIOTTO情24 分钟前
舆情监测系统选型与技术落地:Infoseek 字节探索全栈架构解析与实战
架构
island13141 小时前
CANN ops-nn 算子库深度解析:神经网络计算引擎的底层架构、硬件映射与融合优化机制
人工智能·神经网络·架构
C澒1 小时前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
roman_日积跬步-终至千里1 小时前
【架构实战-Spring】动态数据源切换方案
架构
C澒1 小时前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式
韩立学长2 小时前
基于Springboot泉州旅游攻略平台d5h5zz02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·旅游
晚霞的不甘2 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
C澒2 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
摇滚侠2 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea