现代微服务虽小巧、自治且能独立部署------但这恰恰埋下了一个大问题。
想想那些典型的药房处方管理、支付、银行、物流、供应链或预订系统吧,它们的操作往往横跨多个微服务:
- 创建订单
- 预留库存
- 处理支付
- 创建发货单
- 更新积分
每个服务都运行在各自独立的数据库 、服务 和网络边界内。
如果某个操作半路掉链子,整个业务流程绝不能把系统丢在一个不一致的状态里不管。
这时候,Saga 模式就变得至关重要了。
Saga 是什么?为什么不用 ACID 事务?
一个 Saga 就是一串本地事务的集合。每个本地事务更新单个服务内的数据,并发布一个事件来触发下一步。
一旦发生失败:
- Saga 就会启动补偿操作,回滚前面已经完成的步骤。
为什么 ACID(两阶段提交,2PC)在微服务里玩不转?
| 问题 | 解释 |
|---|---|
| 2PC 是阻塞的 | 要是协调者崩溃,整个系统就卡住了。 |
| 微服务各有独立的数据库 | 分布式数据库之间没有共享的提交日志。 |
| 扩展性差 | 锁会蔓延到整个分布式系统。 |
| 云服务普遍不支持 XA 事务 | DynamoDB, S3, Mongo, Kafka, RDS 等等都不支持。 |
Saga 通过事件驱动或集中编排的补偿逻辑,巧妙地避开了这些问题。
Saga 架构图

1. 实战用例:药房处方订单处理
场景
想象一个在线药房处方平台,客户下单购买一个产品。
这个系统涉及三个独立服务:
- 订单服务 ------ 创建订单记录。
- 库存服务 ------ 预留库存中的产品。
- 支付服务 ------ 向客户收款。
没有 Saga 会怎样?
如果其中任何一步失败(比如支付失败),前面步骤可能导致系统状态不一致:
- 订单已创建
- 库存已预留
- 支付失败
- 结果:库存被卡住,订单不完整,需要人工干预。
这在企业系统中是绝对无法接受的,尤其是在多个微服务交互的场景下。
Saga 的解决方案
使用 Saga 编排模式 ,每个服务执行自己的本地事务 ,一旦失败,就通过补偿操作回滚前面的步骤:
- 步骤 1: 订单服务创建订单。
- 步骤 2: 库存服务预留库存。
- 步骤 3: 支付服务向客户收款。
- 失败处理: 如果支付失败,编排器会调用库存服务释放库存。
这确保了跨服务的业务级别的一致性。
2. 为什么我们需要 Saga 模式?
- 分布式微服务: 传统的 ACID 事务无法跨越多个微服务。
- 最终一致性: 确保最终结果一致,无需全局资源锁。
- 容错性: 如果后续步骤失败,能自动回滚前面的步骤。
- 云原生: 能与云环境中可扩展的服务无缝协作。
松耦合:每个服务保持独立,仅通过 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";
}
}
- 流程解析
- 订单创建 → 第一步,必须成功才能继续。
- 库存预留 → 如果失败,Saga 停止。
- 支付处理 → 如果失败,编排器触发库存释放(补偿)。
- 成功 → 返回成功消息。
企业级就绪: 演示了清晰的编排、补偿和业务级别的一致性。
4.6 输出验证
-
成功案例

-
支付失败案例

-
库存失败案例

5. 核心优势总结
- 容错性强: 自动补偿失败的事务。
- 业务一致性: 确保订单状态和库存始终保持一致。
- 易于扩展: 每个服务独立运行。
- 云原生就绪: 可在 Kubernetes 或任何云环境中运行。
- 企业适用性强: 符合医疗、金融、数据平台等真实世界系统的需求。
6. 企业为何需要这个模式?
- 在分布式系统中, 手动回滚容易出错。
- Saga 确保自动恢复,减少停机时间。
- 防止多服务工作流中出现数据不一致。
- 支持异步处理,提升性能和可扩展性。