背景与问题
只要系统一拆成微服务,事务问题几乎迟早会找上门。
单体时代,我们习惯了数据库本地事务:一条订单、一条库存、一条支付记录,放进一个事务里提交,失败就回滚,简单直接。但到了微服务里,订单服务、库存服务、支付服务、营销服务都各自有独立数据库,再想靠一个 BEGIN/COMMIT/ROLLBACK 管到底,基本就不现实了。
很多团队一开始会问:那能不能上 2PC、XA?
理论上能,实践里经常不划算。原因通常有几个:
- 服务异构,协议和中间件不统一
- 锁持有时间长,吞吐明显下降
- 一个节点卡住,整条链路都受影响
- 云原生环境下,大家更倾向于自治服务,而不是强耦合事务协调器
所以,大多数业务系统最终都会走向最终一致性。而 Saga,就是其中最常见、也最接地气的一种方案。
一个典型业务场景
以“下单”为例,常见流程可能是:
- 创建订单
- 预扣库存
- 冻结优惠券
- 扣减账户余额或发起支付
- 确认订单成功
如果第 4 步失败,前面已经成功的步骤怎么办?这就是分布式事务的核心痛点:
- 不是所有操作都能原子提交
- 不同服务失败时间点不同
- 网络超时不等于业务失败
- 重试会带来重复执行问题
- 补偿并不总是“回滚到原点”
这也是为什么 Saga 模式在微服务里特别常见:它不追求跨服务的强一致,而是通过前向事务 + 逆向补偿实现业务上的最终一致。
核心原理
Saga 可以理解成:把一个长事务拆成一串本地事务,每一步本地提交;一旦后续步骤失败,就按相反顺序执行补偿动作。
Saga 的两种常见实现
1. Choreography:事件编排式
每个服务监听事件并决定自己的下一步动作,没有一个中心协调者。
优点:
- 解耦强
- 扩展新参与方方便
缺点:
- 业务流程分散在多个服务里
- 排查问题困难
- 流程一复杂就容易失控
2. Orchestration:中心协调式
由一个 Saga Orchestrator 负责驱动整条流程,明确下一步该调用谁、失败时补偿谁。
优点:
- 流程集中,便于治理
- 适合中高复杂业务
- 更利于观测与审计
缺点:
- 协调器本身变成关键组件
- 设计不好容易变成“上帝服务”
在实际项目里,如果是订单、履约、退款这类有明确流程的核心链路,我通常更推荐 Orchestration。原因很朴素:后期查问题真的省命。
Saga 执行模型
flowchart LR
A[创建订单] --> B[预扣库存]
B --> C[冻结优惠券]
C --> D[发起支付]
D --> E[确认订单]
D --失败--> C1[解冻优惠券]
C1 --> B1[释放库存]
B1 --> A1[取消订单]
上图就是标准的 Saga 流程:前向事务成功推进,失败时按逆序补偿。
和“数据库回滚”最大的区别
这里有个容易误解的点:补偿不等于数据库回滚。
比如:
- 已发送短信,无法“回滚”,只能补发说明消息
- 已调用第三方支付,可能只能发起退款,而不是撤销扣款
- 已扣减库存,补偿时要判断库存是否还能原路恢复
所以补偿设计的本质,不是机械地撤销,而是让业务重新回到可接受状态。
状态建模很关键
Saga 落地时,一定要显式建模状态,而不是靠日志猜流程。
stateDiagram-v2
[*] --> PENDING
PENDING --> ORDER_CREATED
ORDER_CREATED --> INVENTORY_RESERVED
INVENTORY_RESERVED --> COUPON_FROZEN
COUPON_FROZEN --> PAYMENT_PROCESSING
PAYMENT_PROCESSING --> COMPLETED
PAYMENT_PROCESSING --> COMPENSATING
COUPON_FROZEN --> COMPENSATING
INVENTORY_RESERVED --> COMPENSATING
ORDER_CREATED --> COMPENSATING
COMPENSATING --> COMPENSATED
COMPLETED --> [*]
COMPENSATED --> [*]
建议至少区分这些状态:
PENDING:流程未开始PROCESSING:执行中COMPLETED:全部完成COMPENSATING:补偿中COMPENSATED:补偿完成FAILED:人工介入
如果没有状态机,线上出问题时,你会很难判断:
- 这个请求到底有没有执行到支付?
- 是真正失败,还是消息延迟?
- 补偿执行过没有?
- 是否可以安全重试?
方案对比与取舍分析
分布式事务不是只有 Saga 一条路,但 Saga 的适用面非常广。
Saga vs 2PC/XA
| 维度 | Saga | 2PC/XA |
|---|---|---|
| 一致性 | 最终一致 | 强一致 |
| 性能 | 更高 | 较低 |
| 锁资源占用 | 少 | 多 |
| 实现复杂度 | 业务复杂 | 基础设施复杂 |
| 适用场景 | 互联网业务、长流程 | 金融核心强一致场景 |
| 故障隔离 | 较好 | 较差 |
我的经验是:
- 库存、订单、优惠券、积分这类业务,很适合 Saga
- 账户总账、核心清结算这类强一致要求极高的场景,要慎用 Saga
Saga vs TCC
| 维度 | Saga | TCC |
|---|---|---|
| 侵入性 | 中等 | 高 |
| 业务改造量 | 较少 | 较大 |
| 一致性控制 | 较弱 | 更强 |
| 补偿方式 | 反向补偿 | Try/Confirm/Cancel |
| 适用流程 | 长事务、异步链路 | 核心资源预留型流程 |
如果你的业务天然适合“资源预留”,例如账户冻结金额、库存冻结,那 TCC 往往更稳;如果流程跨多个异步节点、第三方依赖较多,Saga 往往更现实。
设计落地:一个可运行的 Saga 示例
下面我用 Python 做一个简化但可运行的示例,模拟一个订单 Saga 协调器。它不依赖复杂中间件,先把核心机制跑通:步骤执行、失败补偿、幂等保护、状态记录。
目录设计思路
这个 Demo 包含:
OrderService:创建/取消订单InventoryService:预扣/释放库存PaymentService:扣款/退款SagaOrchestrator:统一驱动流程
核心时序图
sequenceDiagram
participant Client
participant Saga
participant Order
participant Inventory
participant Payment
Client->>Saga: 提交下单请求
Saga->>Order: createOrder()
Order-->>Saga: success
Saga->>Inventory: reserve()
Inventory-->>Saga: success
Saga->>Payment: charge()
alt 支付成功
Payment-->>Saga: success
Saga-->>Client: 下单成功
else 支付失败
Payment-->>Saga: fail
Saga->>Inventory: release()
Saga->>Order: cancelOrder()
Saga-->>Client: 下单失败,已补偿
end
可运行代码
from dataclasses import dataclass, field
from typing import Dict, List, Callable
import uuid
class SagaStepError(Exception):
pass
@dataclass
class SagaContext:
saga_id: str
order_id: str = ""
user_id: str = ""
product_id: str = ""
amount: int = 0
inventory_count: int = 0
payment_charged: bool = False
order_created: bool = False
inventory_reserved: bool = False
status: str = "PENDING"
logs: List[str] = field(default_factory=list)
def log(self, msg: str):
self.logs.append(msg)
print(f"[{self.saga_id}] {msg}")
class IdempotencyStore:
def __init__(self):
self.executed_keys = set()
def executed(self, key: str) -> bool:
return key in self.executed_keys
def mark(self, key: str):
self.executed_keys.add(key)
class OrderService:
def __init__(self):
self.orders: Dict[str, str] = {}
def create_order(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"create_order:{ctx.saga_id}"
if idem.executed(key):
ctx.log("create_order 幂等命中,跳过")
return
ctx.order_id = f"ORD-{uuid.uuid4().hex[:8]}"
self.orders[ctx.order_id] = "CREATED"
ctx.order_created = True
idem.mark(key)
ctx.log(f"订单已创建: {ctx.order_id}")
def cancel_order(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"cancel_order:{ctx.saga_id}"
if idem.executed(key):
ctx.log("cancel_order 幂等命中,跳过")
return
if ctx.order_id and self.orders.get(ctx.order_id) == "CREATED":
self.orders[ctx.order_id] = "CANCELED"
ctx.log(f"订单已取消: {ctx.order_id}")
else:
ctx.log("订单取消时未找到可取消订单,视为幂等成功")
idem.mark(key)
class InventoryService:
def __init__(self):
self.stock = {"SKU-1": 10}
self.reserved: Dict[str, int] = {}
def reserve(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"reserve_inventory:{ctx.saga_id}"
if idem.executed(key):
ctx.log("reserve_inventory 幂等命中,跳过")
return
available = self.stock.get(ctx.product_id, 0)
if available < ctx.inventory_count:
raise SagaStepError("库存不足")
self.stock[ctx.product_id] -= ctx.inventory_count
self.reserved[ctx.saga_id] = ctx.inventory_count
ctx.inventory_reserved = True
idem.mark(key)
ctx.log(f"库存预扣成功: {ctx.product_id} x {ctx.inventory_count}")
def release(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"release_inventory:{ctx.saga_id}"
if idem.executed(key):
ctx.log("release_inventory 幂等命中,跳过")
return
count = self.reserved.get(ctx.saga_id, 0)
if count > 0:
self.stock[ctx.product_id] += count
del self.reserved[ctx.saga_id]
ctx.log(f"库存已释放: {ctx.product_id} x {count}")
else:
ctx.log("未发现预扣库存,视为幂等成功")
idem.mark(key)
class PaymentService:
def __init__(self, fail_on_charge: bool = False):
self.balance = {"U-1": 1000}
self.charged: Dict[str, int] = {}
self.fail_on_charge = fail_on_charge
def charge(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"charge_payment:{ctx.saga_id}"
if idem.executed(key):
ctx.log("charge_payment 幂等命中,跳过")
return
if self.fail_on_charge:
raise SagaStepError("支付渠道超时/失败")
user_balance = self.balance.get(ctx.user_id, 0)
if user_balance < ctx.amount:
raise SagaStepError("余额不足")
self.balance[ctx.user_id] -= ctx.amount
self.charged[ctx.saga_id] = ctx.amount
ctx.payment_charged = True
idem.mark(key)
ctx.log(f"支付扣款成功: {ctx.amount}")
def refund(self, ctx: SagaContext, idem: IdempotencyStore):
key = f"refund_payment:{ctx.saga_id}"
if idem.executed(key):
ctx.log("refund_payment 幂等命中,跳过")
return
amount = self.charged.get(ctx.saga_id, 0)
if amount > 0:
self.balance[ctx.user_id] += amount
del self.charged[ctx.saga_id]
ctx.log(f"支付已退款: {amount}")
else:
ctx.log("未发现扣款记录,视为幂等成功")
idem.mark(key)
@dataclass
class SagaStep:
name: str
action: Callable[[SagaContext, IdempotencyStore], None]
compensation: Callable[[SagaContext, IdempotencyStore], None]
class SagaOrchestrator:
def __init__(self, steps: List[SagaStep], idem: IdempotencyStore):
self.steps = steps
self.idem = idem
def execute(self, ctx: SagaContext):
completed_steps = []
ctx.status = "PROCESSING"
ctx.log("Saga 开始执行")
try:
for step in self.steps:
ctx.log(f"执行步骤: {step.name}")
step.action(ctx, self.idem)
completed_steps.append(step)
ctx.status = "COMPLETED"
ctx.log("Saga 执行成功")
return True
except Exception as e:
ctx.status = "COMPENSATING"
ctx.log(f"Saga 执行失败: {str(e)},开始补偿")
for step in reversed(completed_steps):
try:
ctx.log(f"补偿步骤: {step.name}")
step.compensation(ctx, self.idem)
except Exception as ce:
ctx.log(f"补偿失败: {step.name}, error={str(ce)}")
ctx.status = "FAILED"
return False
ctx.status = "COMPENSATED"
ctx.log("Saga 补偿完成")
return False
def main():
idem = IdempotencyStore()
order_service = OrderService()
inventory_service = InventoryService()
payment_service = PaymentService(fail_on_charge=True)
steps = [
SagaStep(
name="创建订单",
action=order_service.create_order,
compensation=order_service.cancel_order
),
SagaStep(
name="预扣库存",
action=inventory_service.reserve,
compensation=inventory_service.release
),
SagaStep(
name="支付扣款",
action=payment_service.charge,
compensation=payment_service.refund
)
]
orchestrator = SagaOrchestrator(steps, idem)
ctx = SagaContext(
saga_id=f"SAGA-{uuid.uuid4().hex[:8]}",
user_id="U-1",
product_id="SKU-1",
amount=200,
inventory_count=2
)
success = orchestrator.execute(ctx)
print("\n========== 执行结果 ==========")
print("success =", success)
print("status =", ctx.status)
print("orders =", order_service.orders)
print("stock =", inventory_service.stock)
print("balance =", payment_service.balance)
if __name__ == "__main__":
main()
运行效果说明
这段代码里我故意把 PaymentService(fail_on_charge=True) 打开,让支付步骤失败。执行后会看到:
- 订单先创建成功
- 库存预扣成功
- 支付失败
- 开始逆序补偿
- 释放库存
- 取消订单
这就是一个最小可验证的 Saga Orchestration 实现。
从 Demo 到生产,还差什么?
Demo 只是说明机制,真正上线至少还要补这些能力:
- Saga 实例持久化
- 每个步骤的执行日志和状态落库
- 重试策略
- 补偿失败告警
- 超时扫描任务
- 消息投递与 Outbox
- 分布式追踪
实战落地的关键设计点
1. 每个步骤都必须幂等
这是 Saga 成败的第一原则。
为什么?因为你无法避免这些情况:
- 调用超时,但对方其实已经成功
- 消息重复投递
- 协调器崩溃后恢复重放
- 补偿任务被重复触发
所以正向动作和补偿动作都要幂等。常见做法:
- 使用
saga_id + step_name作为唯一幂等键 - 执行前查表
- 执行成功后落幂等记录
- 对“已完成”直接返回成功
可以用一张表保存幂等状态:
CREATE TABLE saga_step_execution (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
saga_id VARCHAR(64) NOT NULL,
step_name VARCHAR(64) NOT NULL,
action_type VARCHAR(16) NOT NULL,
status VARCHAR(16) NOT NULL,
request_id VARCHAR(64),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_saga_step_action (saga_id, step_name, action_type)
);
2. 不要把补偿理解成“绝对还原”
这点我真的建议反复强调。
举个例子:
- 订单已通知商家接单
- 商家已打印小票
- 用户支付后又触发退款补偿
这时你无法让世界回到“什么都没发生”的状态,只能做到:
- 订单标记取消/退款中
- 商家收到撤单通知
- 资金退款
- 审计可追踪
也就是说,Saga 的补偿是业务语义补偿,不是技术回滚幻觉。
3. 前向操作尽量“可补偿”
设计步骤时,优先把不可逆动作放后面。
例如下单流程可以调整为:
- 创建订单
- 预扣库存
- 冻结优惠券
- 发起支付
- 通知仓配出库
比起先通知仓配再支付,这样失败面会小很多。
一个经验法则:
- 可撤销、可冻结、可释放的动作尽量前置
- 不可逆、对外可见、代价高的动作尽量后置
4. 用 Outbox 保证“本地事务 + 发消息”一致
这是很多团队真正踩坑的地方。
比如订单服务做了两件事:
- 本地数据库把订单状态改成
CREATED - 发 MQ 消息通知库存服务
如果数据库提交成功了,但 MQ 发失败了,就会出现状态撕裂。
解决思路通常是 Transactional Outbox:
- 在本地事务中同时写业务表和
outbox_event表 - 后台投递器异步扫描
outbox_event - 投递成功后更新事件状态
- 消费方按业务键幂等处理
示例表结构:
CREATE TABLE outbox_event (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
event_id VARCHAR(64) NOT NULL,
aggregate_id VARCHAR(64) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload JSON NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'NEW',
retry_count INT NOT NULL DEFAULT 0,
next_retry_time TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_event_id (event_id)
);
常见坑与排查
这部分我尽量写得“像线上会遇到的事”,因为 Saga 真正难的不是概念,而是异常路径。
坑 1:超时后重复重试,导致多扣库存或多扣款
现象
- 上游调用超时
- 协调器认为失败并重试
- 下游其实第一次已经成功了
排查重点
- 是否有统一幂等键
- 是“请求超时”还是“业务失败”
- 下游是否把“重复请求”识别为成功
建议
- 所有步骤都带
saga_id或request_id - 超时默认视为“未知”,不要直接当失败
- 先查执行状态,再决定是否重试或补偿
坑 2:补偿成功了一半,流程卡死
现象
- 库存释放成功
- 订单取消失败
- Saga 状态长时间停留在
COMPENSATING
排查重点
- 补偿任务是否有重试机制
- 补偿失败是否落库
- 是否有人工处理台账
建议
- 补偿失败不要静默
- 进入
FAILED后触发告警 - 提供人工重试和人工关闭入口
坑 3:消息乱序导致状态覆盖
现象
- “支付成功”消息晚于“退款成功”消息到达
- 订单状态被错误覆盖为已支付
排查重点
- 状态更新是否有版本控制
- 是否允许状态逆行
- 是否根据事件时间盲目覆盖
建议
- 建立合法状态流转表
- 使用版本号或状态机校验
- 不允许低优先级事件覆盖终态
例如:
UPDATE orders
SET status = 'PAID', version = version + 1
WHERE order_id = 'ORD-1001'
AND version = 3
AND status IN ('PAYING');
坑 4:把补偿设计成同步强依赖,故障雪崩
现象
- 支付失败
- 补偿依赖多个服务同步响应
- 某个服务挂掉,整条链路持续阻塞
建议
- 补偿动作尽量支持异步化
- 关键步骤本地落状态后再异步恢复
- 不要让用户请求线程长时间等待所有补偿完成
坑 5:只记录日志,不记录 Saga 状态
现象
- 日志里能看到“可能执行过”
- 数据库里却没有明确状态
- 运维和开发靠猜定位
建议
至少落这几类数据:
- Saga 实例主表
- Saga 步骤明细表
- 幂等执行表
- 事件投递表
- 异常补偿记录表
安全/性能最佳实践
Saga 不只是“事务问题”,还会牵扯到安全、性能、容量和稳定性。
安全实践
1. 补偿接口必须鉴权
很多团队会把补偿接口暴露成内部 HTTP API,但如果没有服务间鉴权,风险很大。
建议:
- 只允许内网访问
- 使用 mTLS、JWT 或网关签名
- 校验调用方服务身份
- 记录审计日志
2. 避免在消息中传递敏感明文
订单 Saga 的事件里可能带:
- 用户手机号
- 支付流水号
- 地址信息
建议:
- 只传业务必要字段
- 敏感字段脱敏或加密
- 日志避免打印完整支付信息
3. 人工补偿入口要有权限隔离
人工重试、强制关闭 Saga、跳过某个步骤,这些功能非常敏感。
建议至少区分:
- 只读排查权限
- 运维重试权限
- 财务/风控审批权限
性能实践
1. Saga 协调器不要串行阻塞一切
如果每个步骤都同步串行等待,很快会把吞吐打下来。可优化方式:
- 对弱依赖步骤改异步
- 超时快速返回“处理中”
- 用状态回查替代长连接等待
2. 重试要有退避策略
不要固定间隔无脑重试,否则故障时会放大流量。
建议:
- 指数退避
- 加随机抖动
- 限制最大重试次数
- 区分技术错误和业务错误
示例:
import time
import random
def retry_with_backoff(fn, max_retry=5, base=0.5):
for i in range(max_retry):
try:
return fn()
except Exception as e:
if i == max_retry - 1:
raise
sleep_time = base * (2 ** i) + random.uniform(0, 0.3)
time.sleep(sleep_time)
3. 容量估算别只算主链路,还要算补偿洪峰
这是架构设计里非常容易漏掉的一点。
如果平时每秒有 1000 笔下单,而支付渠道故障 5 分钟:
- 正向请求会堆积
- 重试任务会增加
- 补偿任务会暴增
- 状态扫描器也会产生额外压力
所以容量至少要评估:
- 峰值 Saga 创建速率
- 平均步骤数
- 消息投递速率
- 重试倍增因子
- 补偿峰值并发
一个简化估算公式:
总操作量 ≈ 事务数 × 平均步骤数 × (1 + 重试系数 + 补偿系数)
例如:
- 2000 TPS 下单
- 平均 4 个步骤
- 重试系数 0.2
- 补偿系数 0.1
则总操作量约为:
2000 × 4 × (1 + 0.2 + 0.1) = 10400 ops/s
这还没算日志、追踪、监控写入。
可观测性实践
必须有统一追踪字段
建议全链路统一透传:
trace_idsaga_idorder_idstep_namerequest_id
必须有核心监控指标
至少监控这些:
- Saga 成功率
- Saga 补偿率
- Saga 平均耗时 / P95 / P99
- 各步骤失败率
- 补偿失败率
- 超时扫描命中数
- Outbox 堆积量
- 重试队列长度
没有这些指标,线上出问题时你会非常被动。
一套更接近生产的表结构建议
下面给一个简化版建模,适合大多数中型业务做起步。
Saga 主表
CREATE TABLE saga_instance (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
saga_id VARCHAR(64) NOT NULL,
business_id VARCHAR(64) NOT NULL,
saga_type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
current_step VARCHAR(64),
payload JSON,
error_message VARCHAR(512),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_saga_id (saga_id),
KEY idx_business_id (business_id),
KEY idx_status_updated_at (status, updated_at)
);
Saga 步骤表
CREATE TABLE saga_step (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
saga_id VARCHAR(64) NOT NULL,
step_name VARCHAR(64) NOT NULL,
step_order INT NOT NULL,
action_status VARCHAR(16) NOT NULL,
compensation_status VARCHAR(16) NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
last_error VARCHAR(512),
started_at TIMESTAMP NULL,
finished_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_saga_step (saga_id, step_name)
);
有了这两张表,很多线上问题都能快速回答:
- 事务卡在哪一步?
- 正向执行成功了几步?
- 补偿执行到了哪里?
- 是否需要人工介入?
落地建议:什么时候适合用 Saga,什么时候别硬上
适合用 Saga 的场景
- 跨多个微服务、多个数据库
- 接受最终一致性
- 步骤可定义补偿动作
- 链路较长,存在异步处理
- 对吞吐和可用性要求高于强一致
不适合直接用 Saga 的场景
- 强一致要求极高,且不能接受短暂不一致
- 核心动作不可逆,补偿成本巨大
- 流程过于灵活、变化频繁,难以建模
- 团队还没有幂等、消息、可观测性基础能力
如果基础设施还比较薄弱,我更建议先把这些打牢:
- 幂等机制
- 消息可靠投递
- 状态机建模
- 可观测性和告警
- 人工补偿后台
没有这些,Saga 很容易从“解决问题”变成“制造更隐蔽的问题”。
总结
Saga 模式的核心,不是某个框架,也不是几段补偿代码,而是一整套面向失败设计的思路:
- 把长事务拆成多个本地事务
- 每一步都可追踪、可重试、可补偿
- 用幂等抵御重复执行
- 用状态机约束流程演进
- 用 Outbox 解决本地提交与消息发送的一致性
- 用监控、审计和人工介入兜住异常尾巴
如果你准备在微服务里落地 Saga,我给的可执行建议是:
- 先选一个链路清晰的业务试点,比如下单-库存-支付
- 优先做 Orchestration,先把流程收拢,便于观测
- 先把状态表、步骤表、幂等表建起来
- 每个步骤先写正向接口,再写补偿接口
- 把“超时、重试、重复消息”当正常情况设计
- 为补偿失败准备人工处理机制
最后给一个边界判断:如果你的业务无法接受短暂不一致,或者补偿代价远高于锁住资源的代价,那 Saga 可能不是最优解;但如果你的目标是在复杂微服务场景下,平衡一致性、性能和可用性,Saga 依然是非常值得投入的一种工程化方案。