写在前面:说实话,我第一次在项目里遇到"下单扣库存扣余额加积分发通知"这种跨5个服务的长流程时,脑子里第一反应就是TCC。结果写了一周Try/Confirm/Cancel三套代码后,人差点没了------每个服务都要改,侵入性太强了。后来组长说用Saga模式吧,我才恍然大悟:原来长事务编排还有更优雅的解法。这篇文章就把Saga的原理、实现、踩坑经验一次讲清楚,希望对你有用。

文章目录
-
- 一、为什么需要Saga?TCC不够用吗?
- 二、Saga模式原理
- 三、Saga两种实现方式
-
- [1. 编排式(Orchestration)](#1. 编排式(Orchestration))
- [2. 协同式(Choreography)](#2. 协同式(Choreography))
- 两种方式对比
- [四、Seata Saga实现](#四、Seata Saga实现)
-
- Saga状态机设计
- JSON状态机配置文件示例
- 完整Java代码示例:电商下单Saga编排
-
- [OrderService(创建订单 + 补偿:取消订单)](#OrderService(创建订单 + 补偿:取消订单))
- [InventoryService(扣减库存 + 补偿:恢复库存)](#InventoryService(扣减库存 + 补偿:恢复库存))
- [PaymentService(扣减余额 + 补偿:退还余额)](#PaymentService(扣减余额 + 补偿:退还余额))
- [PointsService(增加积分 + 补偿:扣减积分)](#PointsService(增加积分 + 补偿:扣减积分))
- Saga编排入口
- 五、Saga的补偿设计原则
-
- [1. 补偿操作必须是幂等的](#1. 补偿操作必须是幂等的)
- [2. 补偿操作必须能处理"部分成功"](#2. 补偿操作必须能处理"部分成功")
- [3. 补偿操作的顺序必须与正向操作相反](#3. 补偿操作的顺序必须与正向操作相反)
- [4. 超时处理](#4. 超时处理)
- 六、踩坑指南
-
- 补偿操作失败怎么办?
- 循环依赖问题
- 数据不一致的窗口期
- [Saga vs TCC vs 消息最终一致性选型](#Saga vs TCC vs 消息最终一致性选型)
- 七、问题与解答
-
- Q1:Saga和TCC到底怎么选?
- Q2:Saga的补偿操作失败了怎么办?整个事务就卡住了吗?
- [Q3:Seata Saga和手动实现Saga有什么区别?](#Q3:Seata Saga和手动实现Saga有什么区别?)
- 八、面试高频考点汇总
- 九、模拟面试官提问和参考答案
- 互动话题
- 参考资料
一、为什么需要Saga?TCC不够用吗?
场景引入
先看一个典型的电商下单流程:
创建订单 → 扣减库存 → 扣减余额 → 增加积分 → 发送通知
这里有5个服务参与,如果用TCC,每个服务都要写Try/Confirm/Cancel三套代码。
我见过太多人一上来就选TCC,结果写到最后发现:
- 代码量翻三倍:每个服务三套逻辑
- 侵入性太强:原有业务代码要大改
- 维护成本高:改一个流程要改多个服务的Try/Confirm/Cancel
TCC vs Saga对比
| 维度 | TCC | Saga |
|---|---|---|
| 适用事务长度 | 短事务(2-3步) | 长事务(5+步) |
| 代码侵入性 | 高(三套代码) | 低(正向+补偿) |
| 一致性 | 强一致(两阶段) | 最终一致 |
| 实现复杂度 | 中等 | 较低 |
| 性能 | 较低(资源锁定) | 较高(无锁定) |
| 典型场景 | 银行转账 | 电商下单、旅行预订 |
生活类比
装修房子:
- TCC = 每步都先"预占"------先搬家具进来试试,不行再搬走,再试下一件。每件家具都要试三遍,累不累?
- Saga = 直接干,一件一件按顺序来。发现某步不行了,就把前面干过的"拆回去"(补偿)。简单粗暴,但效率高。
二、Saga模式原理
正向操作与补偿操作
Saga的核心思想就两步:
正向操作:按顺序执行所有步骤
T1(创建订单)→ T2(扣减库存)→ T3(扣减余额)→ T4(增加积分)→ T5(发送通知)
补偿操作:某一步失败后,按反向顺序补偿已成功的步骤
C5(撤回通知)→ C4(扣减积分)→ C3(退还余额)→ C2(恢复库存)→ C1(取消订单)
流程说明
正常流程(全部成功):
T1 → T2 → T3 → T4 → T5 ✓ 全部完成,事务成功
异常流程(T3失败):
T1 ✓ → T2 ✓ → T3 ✗(扣减余额失败)
↓ 触发补偿
C2(恢复库存)→ C1(取消订单)
注意:T3失败了,只需要补偿T1和T2(已经成功的步骤),T3本身不需要补偿,T4和T5根本没执行也不需要补偿。
三、Saga两种实现方式
1. 编排式(Orchestration)
有一个中央协调器(Saga Manager)来控制整个流程的执行顺序。
┌─────────────────┐
│ Saga Manager │
│ (中央协调器) │
└────────┬────────┘
│ 调用
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│订单服务│ │库存服务│
└───────┘ └───────┘
│ │
▼ ▼
┌───────┐ ┌───────┐
│支付服务│ │积分服务│
└───────┘ └───────┘
优点:
- 集中管理,流程清晰,一眼就能看出整个事务的执行路径
- 方便做超时处理、重试策略
- Seata Saga就是这种实现
缺点:
- 协调器存在单点风险
- 所有服务都要和协调器通信,耦合度相对较高
2. 协同式(Choreography)
没有中央协调器,各服务自主监听事件,通过事件驱动完成流程。
订单服务 --[订单创建事件]--> 库存服务
库存服务 --[库存扣减事件]--> 支付服务
支付服务 --[支付完成事件]--> 积分服务
积分服务 --[积分增加事件]--> 通知服务
如果某一步失败,发送补偿事件,反向传播。
优点:
- 去中心化,没有单点风险
- 服务之间松耦合,通过事件通信
- 适合简单流程
缺点:
- 流程难以追踪,出了问题不好排查
- 服务之间可能产生循环依赖
- 复杂流程下事件链路太长,难以维护
两种方式对比
| 维度 | 编排式(Orchestration) | 协同式(Choreography) |
|---|---|---|
| 控制方式 | 中央协调器 | 事件驱动,去中心化 |
| 耦合度 | 中(与协调器耦合) | 低(事件解耦) |
| 流程可见性 | 高(集中定义) | 低(分散在各服务) |
| 单点风险 | 有(协调器) | 无 |
| 适用场景 | 复杂长事务 | 简单短事务 |
| 代表实现 | Seata Saga | Spring Cloud Event |
四、Seata Saga实现
Saga状态机设计
Seata Saga通过状态机来定义事务流程。核心概念:
- State:一个操作步骤(服务调用)
- Task:具体的服务调用任务
- Choice:条件分支(成功走正向,失败走补偿)
- Compensation:补偿操作
JSON状态机配置文件示例
json
{
"Name": "createOrderSaga",
"Comment": "电商下单Saga编排",
"StartState": "CreateOrder",
"States": {
"CreateOrder": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "createOrder",
"CompensateState": "CancelOrder",
"Next": "DeductInventory"
},
"DeductInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryService",
"ServiceMethod": "deductInventory",
"CompensateState": "RestoreInventory",
"Next": "DeductBalance"
},
"DeductBalance": {
"Type": "ServiceTask",
"ServiceName": "paymentService",
"ServiceMethod": "deductBalance",
"CompensateState": "RefundBalance",
"Next": "AddPoints"
},
"AddPoints": {
"Type": "ServiceTask",
"ServiceName": "pointsService",
"ServiceMethod": "addPoints",
"CompensateState": "SubtractPoints",
"Next": "SendNotification"
},
"SendNotification": {
"Type": "ServiceTask",
"ServiceName": "notificationService",
"ServiceMethod": "sendNotification",
"CompensateState": "WithdrawNotification",
"Next": "Succeed"
},
"CancelOrder": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "cancelOrder"
},
"RestoreInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryService",
"ServiceMethod": "restoreInventory"
},
"RefundBalance": {
"Type": "ServiceTask",
"ServiceName": "paymentService",
"ServiceMethod": "refundBalance"
},
"SubtractPoints": {
"Type": "ServiceTask",
"ServiceName": "pointsService",
"ServiceMethod": "subtractPoints"
},
"WithdrawNotification": {
"Type": "ServiceTask",
"ServiceName": "notificationService",
"ServiceMethod": "withdrawNotification"
},
"Succeed": {
"Type": "Succeed"
}
}
}
完整Java代码示例:电商下单Saga编排
OrderService(创建订单 + 补偿:取消订单)
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单 - Saga正向操作
*/
@Transactional
public Order createOrder(Long userId, Long productId, Integer quantity, BigDecimal amount) {
Order order = new Order();
order.setOrderId(IdGenerator.nextId());
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setAmount(amount);
order.setStatus(OrderStatus.CREATED.getStatus());
order.setCreateTime(new Date());
orderMapper.insert(order);
return order;
}
/**
* 取消订单 - Saga补偿操作(幂等)
*/
@Transactional
public void cancelOrder(String orderId) {
Order order = orderMapper.selectByOrderId(orderId);
if (order == null) {
// 幂等处理:订单已不存在,直接返回
return;
}
// 只有已创建状态的订单才能取消
if (OrderStatus.CREATED.getStatus().equals(order.getStatus())) {
order.setStatus(OrderStatus.CANCELLED.getStatus());
order.setCancelTime(new Date());
orderMapper.updateStatus(order);
}
// 其他状态说明已经被处理过了,幂等返回
}
}
InventoryService(扣减库存 + 补偿:恢复库存)
java
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
/**
* 扣减库存 - Saga正向操作
*/
@Transactional
public void deductInventory(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory == null || inventory.getStock() < quantity) {
throw new SagaException("库存不足,productId=" + productId);
}
inventory.setStock(inventory.getStock() - quantity);
inventory.setLockedStock(inventory.getLockedStock() + quantity);
inventoryMapper.updateStock(inventory);
}
/**
* 恢复库存 - Saga补偿操作(幂等)
*/
@Transactional
public void restoreInventory(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory == null) {
return; // 幂等处理
}
// 恢复库存,同时减少锁定库存
inventory.setStock(inventory.getStock() + quantity);
inventory.setLockedStock(Math.max(0, inventory.getLockedStock() - quantity));
inventoryMapper.updateStock(inventory);
}
}
PaymentService(扣减余额 + 补偿:退还余额)
java
@Service
public class PaymentService {
@Autowired
private UserAccountMapper accountMapper;
@Autowired
private PaymentRecordMapper paymentRecordMapper;
/**
* 扣减余额 - Saga正向操作
*/
@Transactional
public void deductBalance(Long userId, BigDecimal amount, String bizNo) {
// 幂等检查:是否已经扣减过
PaymentRecord existRecord = paymentRecordMapper.selectByBizNo(bizNo);
if (existRecord != null) {
return; // 已处理,幂等返回
}
UserAccount account = accountMapper.selectByUserId(userId);
if (account == null || account.getBalance().compareTo(amount) < 0) {
throw new SagaException("余额不足,userId=" + userId);
}
account.setBalance(account.getBalance().subtract(amount));
accountMapper.updateBalance(account);
// 记录支付流水
PaymentRecord record = new PaymentRecord();
record.setBizNo(bizNo);
record.setUserId(userId);
record.setAmount(amount);
record.setType(PaymentType.DEDUCT.getType());
record.setCreateTime(new Date());
paymentRecordMapper.insert(record);
}
/**
* 退还余额 - Saga补偿操作(幂等)
*/
@Transactional
public void refundBalance(Long userId, BigDecimal amount, String bizNo) {
// 幂等检查
PaymentRecord refundRecord = paymentRecordMapper.selectByBizNo("REFUND_" + bizNo);
if (refundRecord != null) {
return; // 已退款,幂等返回
}
UserAccount account = accountMapper.selectByUserId(userId);
if (account == null) {
return;
}
account.setBalance(account.getBalance().add(amount));
accountMapper.updateBalance(account);
// 记录退款流水
PaymentRecord record = new PaymentRecord();
record.setBizNo("REFUND_" + bizNo);
record.setUserId(userId);
record.setAmount(amount);
record.setType(PaymentType.REFUND.getType());
record.setCreateTime(new Date());
paymentRecordMapper.insert(record);
}
}
PointsService(增加积分 + 补偿:扣减积分)
java
@Service
public class PointsService {
@Autowired
private UserPointsMapper pointsMapper;
@Autowired
private PointsRecordMapper recordMapper;
/**
* 增加积分 - Saga正向操作
*/
@Transactional
public void addPoints(Long userId, Integer points, String bizNo) {
// 幂等检查
PointsRecord existRecord = recordMapper.selectByBizNo(bizNo);
if (existRecord != null) {
return;
}
UserPoints userPoints = pointsMapper.selectByUserId(userId);
if (userPoints == null) {
userPoints = new UserPoints();
userPoints.setUserId(userId);
userPoints.setTotalPoints(0);
userPoints.setAvailablePoints(0);
pointsMapper.insert(userPoints);
}
userPoints.setTotalPoints(userPoints.getTotalPoints() + points);
userPoints.setAvailablePoints(userPoints.getAvailablePoints() + points);
pointsMapper.updatePoints(userPoints);
// 记录积分流水
PointsRecord record = new PointsRecord();
record.setBizNo(bizNo);
record.setUserId(userId);
record.setPoints(points);
record.setType(PointsType.EARN.getType());
record.setCreateTime(new Date());
recordMapper.insert(record);
}
/**
* 扣减积分 - Saga补偿操作(幂等)
*/
@Transactional
public void subtractPoints(Long userId, Integer points, String bizNo) {
// 幂等检查
PointsRecord existRecord = recordMapper.selectByBizNo("SUBTRACT_" + bizNo);
if (existRecord != null) {
return;
}
UserPoints userPoints = pointsMapper.selectByUserId(userId);
if (userPoints == null) {
return;
}
// 可用积分不足时,扣到0为止(部分补偿场景)
int actualSubtract = Math.min(points, userPoints.getAvailablePoints());
userPoints.setAvailablePoints(userPoints.getAvailablePoints() - actualSubtract);
pointsMapper.updatePoints(userPoints);
// 记录扣减流水
PointsRecord record = new PointsRecord();
record.setBizNo("SUBTRACT_" + bizNo);
record.setUserId(userId);
record.setPoints(actualSubtract);
record.setType(PointsType.SUBTRACT.getType());
record.setCreateTime(new Date());
recordMapper.insert(record);
}
}
Saga编排入口
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private SagaStateMachineEngine sagaStateMachineEngine;
/**
* 发起Saga下单流程
*/
@PostMapping("/create")
public Result<String> createOrder(@RequestBody CreateOrderRequest request) {
// 构建Saga状态机输入参数
Map<String, Object> startParams = new HashMap<>();
startParams.put("userId", request.getUserId());
startParams.put("productId", request.getProductId());
startParams.put("quantity", request.getQuantity());
startParams.put("amount", request.getAmount());
startParams.put("bizNo", IdGenerator.nextId().toString());
// 启动Saga状态机
StateMachineInstance instance = sagaStateMachineEngine.startWithJSON(
"createOrderSaga", // 状态机名称
null, // 业务key
startParams // 输入参数
);
if (instance.isSuccess()) {
return Result.success("下单成功,orderId=" + instance.getStateMachine().getBusinessKey());
} else {
return Result.fail("下单失败,已触发补偿:" + instance.getException().getMessage());
}
}
}
五、Saga的补偿设计原则
1. 补偿操作必须是幂等的
这个坑我踩过。补偿操作可能被重复调用------网络超时导致协调器以为补偿失败了,再调一次。如果补偿操作不幂等,就会出现"退了两次钱"的惨剧。
java
// 正确做法:通过业务流水号保证幂等
@Transactional
public void refundBalance(Long userId, BigDecimal amount, String bizNo) {
// 先查流水,存在就说明已经退过了
PaymentRecord record = paymentRecordMapper.selectByBizNo("REFUND_" + bizNo);
if (record != null) {
log.info("重复补偿请求,已处理过,bizNo={}", bizNo);
return;
}
// 执行退款逻辑...
}
2. 补偿操作必须能处理"部分成功"
比如扣减积分成功了100分,但补偿时用户已经消费了30分,只剩70分可退。这时候不能直接报错,要退能退的部分,差额走人工处理。
3. 补偿操作的顺序必须与正向操作相反
正向:T1 → T2 → T3 → T4 → T5
补偿:C5 → C4 → C3 → C2 → C1
为什么?因为后面的操作可能依赖前面的结果。比如积分依赖支付,你得先把积分退了,再退钱,否则逻辑上说不通。
4. 超时处理
某一步长时间未响应怎么办?
java
// Seata Saga中可以设置超时
"CreateOrder": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "createOrder",
"CompensateState": "CancelOrder",
"Next": "DeductInventory",
"Retry": [
{
"Exceptions": ["java.net.SocketTimeoutException"],
"IntervalSeconds": 3,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
]
}
六、踩坑指南
踩坑提醒:Saga看起来简单,但真落地的时候坑不少。下面这些是我实际项目中遇到的问题,希望能帮你少走弯路。
补偿操作失败怎么办?
补偿也可能失败。比如退款接口挂了、数据库连不上了。
处理策略:
- 自动重试:设置重试次数和退避策略(指数退避)
- 重试队列:补偿失败的消息进入重试队列,定时消费
- 人工介入:重试超过上限后,生成工单,人工处理
- 告警通知:补偿失败时立即告警
java
// 补偿重试策略示例
public class CompensationRetryPolicy {
private int maxRetries = 5; // 最大重试次数
private long initialInterval = 1000; // 初始间隔1秒
private double multiplier = 2.0; // 退避倍数
public long getRetryInterval(int attempt) {
if (attempt >= maxRetries) {
return -1; // 超过重试次数,需要人工介入
}
return (long) (initialInterval * Math.pow(multiplier, attempt));
}
}
循环依赖问题
A的补偿依赖B的数据,B的补偿又依赖A的数据------这就死锁了。
比如订单取消需要查支付记录,支付退款又需要查订单状态。如果两个表同时被锁,就完了。
解决办法:
- 补偿操作尽量只操作自己的数据,不依赖其他服务
- 如果必须依赖,用缓存 或快照提前保存所需数据
数据不一致的窗口期
Saga是最终一致性,在补偿完成之前,数据是不一致的。比如库存已经扣了,但余额还没扣,这时候查库存是"少了"的。
这个窗口期是Saga的固有特性,业务上要能接受。如果业务要求强一致,别用Saga,老老实实用TCC或者分布式锁。
Saga vs TCC vs 消息最终一致性选型
| 维度 | Saga | TCC | 消息最终一致性 |
|---|---|---|---|
| 一致性级别 | 最终一致 | 强一致(两阶段) | 最终一致 |
| 代码侵入性 | 中(正向+补偿) | 高(三套代码) | 低 |
| 适用步骤数 | 5+步长事务 | 2-3步短事务 | 异步场景 |
| 实时性 | 较高 | 高 | 低(异步) |
| 典型场景 | 电商下单、旅行预订 | 银行转账、支付 | 通知、日志、积分 |
七、问题与解答
Q1:Saga和TCC到底怎么选?
A:看你的事务链路长度和一致性要求。2-3步的短事务,一致性要求高的(比如转账),用TCC。5步以上的长事务,能接受最终一致的,用Saga。别什么都上TCC,代码量会让你怀疑人生。
Q2:Saga的补偿操作失败了怎么办?整个事务就卡住了吗?
A :不会卡住,但需要处理。一般做法是:先自动重试(指数退避),重试超过上限后进入"待人工处理"状态,同时发告警。运维人员介入后手动完成补偿。所以补偿操作一定要有完整的日志记录,方便人工排查。
Q3:Seata Saga和手动实现Saga有什么区别?
A:Seata Saga提供了状态机引擎,帮你管理流程编排、重试、超时、补偿顺序等。手动实现的话,你要自己写协调器逻辑,处理各种边界情况。除非流程特别简单(2-3步),否则建议用Seata Saga,省心。
八、面试高频考点汇总
考点1:什么是Saga模式?解决什么问题?
答案 :Saga是一种长事务编排模式,用于解决跨多个微服务的分布式事务问题。核心思想是:每个服务执行一个正向操作(T),如果某一步失败,则按反向顺序执行已成功步骤的补偿操作(C)。Saga适合5步以上的长事务场景,相比TCC侵入性更低,代码量更少。
考点2:Saga的两种实现方式及区别?
答案:
- 编排式(Orchestration):由中央协调器控制流程,服务之间不直接通信。优点是流程清晰、集中管理;缺点是协调器有单点风险。代表实现是Seata Saga。
- 协同式(Choreography):各服务通过事件自主协作,无中央协调器。优点是去中心化、松耦合;缺点是流程难以追踪。适合简单流程。
考点3:为什么Saga的补偿操作必须是幂等的?
答案 :因为补偿操作可能被重复调用 。网络超时、协调器重试、消息重复投递等情况都可能导致补偿操作被多次执行。如果不幂等,就会出现"退两次款""扣两次积分"等严重问题。幂等性通常通过唯一业务流水号来实现。
考点4:Saga和TCC的一致性有什么区别?
答案 :TCC是两阶段提交 ,Try阶段预留资源,Confirm阶段真正提交,Cancel阶段回滚。在Confirm之前,资源是锁定的,所以能保证强一致性。Saga没有资源预留阶段,正向操作直接提交,失败后通过补偿来恢复,所以是最终一致性,中间存在数据不一致的窗口期。
考点5:Saga模式中如何处理超时?
答案 :在Seata Saga中,可以为每个状态配置超时时间和重试策略。超时后状态机引擎会自动触发补偿流程。重试策略通常采用指数退避(每次重试间隔翻倍),避免短时间内大量重试压垮服务。超过最大重试次数后,进入待人工处理状态。
九、模拟面试官提问和参考答案
场景题1:电商下单涉及6个微服务,你会选TCC还是Saga?为什么?
参考答案:选Saga。6个服务用TCC的话,每个服务要写Try/Confirm/Cancel三套代码,总共18套逻辑,维护成本太高。而且电商下单对一致性要求不是特别严格(最终一致即可),Saga完全够用。只有支付环节如果要求强一致,可以单独对支付服务用TCC,其他服务用Saga编排。
场景题2:补偿操作执行到一半,系统宕机了,怎么保证数据最终一致?
参考答案 :首先,Saga状态机引擎会持久化每一步的执行状态 到数据库。系统恢复后,引擎会检查未完成的事务实例,继续执行未完成的补偿操作。其次,每个补偿操作本身是幂等的,重复执行不会出问题。最后,如果自动恢复失败,会有告警机制通知运维人工介入。关键是:状态持久化 + 补偿幂等 + 告警机制,三管齐下。
场景题3:你的Saga流程中有10个步骤,第7步失败了,补偿到第3步时又失败了,怎么办?
参考答案:第7步失败后,开始反向补偿:C6→C5→C4→C3。C3补偿失败时,引擎会将该补偿任务标记为"重试中",按照指数退避策略自动重试。如果重试超过上限(比如5次),会将整个Saga实例标记为"需要人工介入",同时发送告警。人工处理时,可以通过管理后台查看Saga执行记录,手动触发C3的补偿,或者手动调整数据后标记补偿完成。
场景题4:如何设计一个高可用的Saga协调器?
参考答案:几个关键点:
- 无状态设计:协调器本身不存储事务状态,状态全部持久化到数据库
- 集群部署:多节点部署,通过数据库选主或注册中心选主
- 故障转移:主节点挂了,从节点接管,从数据库恢复未完成的事务
- 限流降级:高并发时对Saga请求做限流,避免协调器过载
- 监控告警:对协调器的QPS、成功率、待补偿事务数做实时监控
场景题5:如果业务要求"下单后库存必须立即扣减,不能有窗口期",还能用Saga吗?
参考答案 :严格来说,Saga是最终一致性,无法保证"立即"。但可以通过以下方式缩短窗口期:
- 将库存扣减放在Saga的第一步,减少后续步骤失败的概率
- 使用同步调用而非异步消息,减少延迟
- 在库存扣减时加预扣机制(类似TCC的Try),给一个短暂的锁定期
但如果业务真的要求强一致的立即扣减,那Saga不适合,应该用TCC或者分布式锁+本地事务的方案。选型要匹配业务需求,不能硬套。
互动话题
你在实际项目中用过Saga模式吗?遇到过什么坑?是用的Seata Saga还是自己实现的?欢迎在评论区分享你的经验,我会在评论区回复讨论。