Saga = 跨服务事务的补偿模式
解决什么问题:分布式场景下不能用 2PC(两阶段提交)做跨服务事务—— 2PC 慢、脆弱、单点故障;Saga = "长事务拆成多个本地事务 + 失败时补偿"。 用"业务补偿"代替"技术锁" = 用业务能接受的方式达到最终一致。
它是什么
Saga 是分布式系统处理跨服务事务的标准模式——
核心理念:
- 每个子事务有对应的补偿操作(如"扣库存" + "释放库存","扣款" + "退款")
- 失败时反向补偿,不是回滚(没有原子性锁可回滚)
- 接受最终一致(补偿完成后,业务状态 = 一致)
两种实现方式:
Choreographed Saga(事件驱动,无中心)
Step 1: OrderService.create() → 发 OrderCreated
Step 2: InventoryService.consume(OrderCreated) → 发 InventoryReserved
Step 3: PaymentService.charge(OrderCreated) → 发 PaymentCompleted
Step 4: ShippingService.ship(OrderCreated) → 发 OrderShipped
任一步失败 → 该服务发失败事件 → 上游服务监听 → 执行补偿
Orchestrated Saga(中央协调器)
OrderOrchestrator.execute():
step1 = inventory.reserve()
if step1.failed: cancel order, return
step2 = payment.charge()
if step2.failed:
inventory.release() # 补偿 step1
cancel order, return
step3 = shipping.ship()
...IDDD Ch 13(Vernon): "Sagas manage long-running business transactions that span multiple Bounded Contexts. Each step has a compensating action to undo the step if a later step fails."
EDM Ch 10(Bellemare): "Sagas prefer business-level consistency over technical atomicity. They acknowledge that distributed transactions across services are impractical and provide a practical alternative."
V2 视角(V2.7 B3 段 5 分解 + B6 段 4 三难): Saga = 承认分布式系统的 CAP 现实——2PC 想要"强一致 + 跨服务",但代价是性能 + 可用性都崩。 Saga 选最终一致 + 业务补偿,放弃"原子性" = 接受代价换工程现实。 这是 B6 权衡的典型案例——分布式事务的"三难"无法绕过,只能选牺牲维度。
关键属性
| 属性 | 值/含义 |
|---|---|
| 无 2PC | 不依赖数据库的分布式事务 |
| 可补偿 | 每个子事务都有对应的反向操作 |
| 最终一致 | 全部成功 OR 全部补偿后 = 业务一致 |
| 隔离性弱 | 中间状态对外可见(订单已建但库存还没扣) |
| 失败可恢复 | 补偿可重试(幂等) |
| 长事务友好 | 跨小时/天的事务也能处理(如订单 → 发货 → 签收 → 复购) |
| 反模式 | 用 Saga 实现"看似必须强一致"的业务 / 补偿不可幂等 |
| 判错标志 | 补偿失败导致业务不一致 / 中间状态对外暴露太久 |
怎么用
场景 1:Orchestrated Saga(中央协调)
class PlaceOrderSaga:
def __init__(self, order_id):
self.order_id = order_id
self.steps = []
def execute(self):
try:
# Step 1: 创建订单
order = self.order_service.create(self.order_id)
self.steps.append(("create_order", order))
# Step 2: 扣库存
self.inventory_service.reserve(self.order_id)
self.steps.append(("reserve_inventory", None))
# Step 3: 收款
self.payment_service.charge(self.order_id)
self.steps.append(("charge_payment", None))
# Step 4: 发货
self.shipping_service.ship(self.order_id)
self.steps.append(("ship_order", None))
return order
except InsufficientStock:
self._compensate()
raise OrderRejected("库存不足")
except PaymentFailed:
self._compensate()
raise OrderRejected("支付失败")
except ShippingFailed:
self._compensate()
raise OrderRejected("发货失败")
def _compensate(self):
# 反向补偿(注意顺序:从后往前)
for step in reversed(self.steps):
action, data = step
if action == "create_order":
self.order_service.cancel(self.order_id)
elif action == "reserve_inventory":
self.inventory_service.release(self.order_id)
elif action == "charge_payment":
self.payment_service.refund(self.order_id)
# ship_order 不需要补偿(发货失败时还没发货)场景 2:Choreographed Saga(事件驱动)
# OrderService
class OrderService:
def place_order(self, cmd):
order = Order.create(cmd)
order_repo.save(order)
event_bus.publish(OrderCreated(order.id, order.items))
# 监听其他服务的失败事件,做补偿
def on_InventoryReservationFailed(self, event):
if event.order_id == self.order_id:
self.order_service.cancel(self.order_id)
def on_PaymentFailed(self, event):
if event.order_id == self.order_id:
self.order_service.cancel(self.order_id)
event_bus.publish(OrderCancelled(self.order_id))
# InventoryService
class InventoryService:
def on_OrderCreated(self, event):
try:
self.inventory.reserve(event.items)
event_bus.publish(InventoryReserved(event.order_id))
except InsufficientStock:
event_bus.publish(InventoryReservationFailed(
event.order_id, reason="库存不足"
))
# 监听支付失败做补偿
def on_PaymentFailed(self, event):
if event.order_id == self.order_id:
self.inventory.release(event.items)
# PaymentService
class PaymentService:
def on_InventoryReserved(self, event):
try:
self.payment.charge(event.order_id, event.amount)
event_bus.publish(PaymentCompleted(event.order_id))
except PaymentDeclined:
event_bus.publish(PaymentFailed(event.order_id))
def on_OrderCancelled(self, event):
if self.has_charged(event.order_id):
self.payment.refund(event.order_id)场景 3:Saga 的 4 个核心设计要点
要点 1:补偿必须幂等
# ✅ 补偿幂等
def release_inventory(order_id):
# 已释放过的不报错
if self.inventory.is_released(order_id):
return
self.inventory.release(order_id)
# ❌ 补偿不幂等(重试会双倍释放)
def release_inventory(order_id):
self.inventory.release(order_id)要点 2:补偿顺序与执行顺序相反
执行顺序: A → B → C
补偿顺序: ~C → ~B → ~A要点 3:中间状态对外可见
订单状态机:
Created → Paid → Shipped → Received
↓
Cancelled (失败时)
中间状态"已创建但未支付"对外可见 ——
这是 Saga 的本质,不是 bug要点 4:超时与重试
class SagaOrchestrator:
TIMEOUT = timedelta(minutes=30)
def execute(self):
future = self.executor.submit(self._run_steps)
try:
return future.result(timeout=self.TIMEOUT)
except TimeoutError:
self._compensate()
raise SagaTimeout()场景 4:Saga 与 2PC 的对比
|| 维度 | Saga | 2PC(两阶段提交)| |---|---|---| | 一致性 | 最终一致 | 强一致 | | 可用性 | 高(每步独立提交)| 低(协调者挂了全锁) | | 性能 | 高(无锁)| 低(全程持锁)| | 隔离性 | 弱(中间状态可见)| 强(持锁期间隔离)| | 故障恢复 | 自动补偿 | 协调者恢复 | | 跨语言 | 友好 | 依赖数据库支持 | | 适用场景 | 跨服务长事务 | 单数据库短事务 |
V2 B6 权衡视角:Saga 放弃"强一致 + 隔离性"换"高可用 + 高性能" = 选 2 个角,放弃 1 个角(CAP 的分布式版本)。
场景 5:长 Saga(跨小时 / 跨天)
# 电商订单的全生命周期 Saga
class OrderLifecycleSaga:
def execute(self, order_id):
# Step 1: 下单(毫秒)
self.place_order(order_id)
# Step 2: 支付(分钟)
wait_for_event(PaymentCompleted, order_id, timeout=15min)
# Step 3: 发货(小时)
wait_for_event(OrderShipped, order_id, timeout=24h)
# Step 4: 签收(天)
wait_for_event(OrderReceived, order_id, timeout=14d)
# Step 5: 复购邀请(天)
schedule_after(7d, self.send_repurchase_invitation, order_id)长 Saga 的关键 = 用事件持久化 + 状态机 + 超时机制,而不是把所有状态放在内存里。
常见误区
- "Saga = 分布式事务"——错。Saga 是没有原子性的事务,明确放弃强一致换工程现实
- "所有跨服务调用都要 Saga"——错。能避免就避免——能异步就异步,能单 BC 就单 BC,Saga 是最后手段
- "补偿 = 回滚"——错。补偿是业务补偿(可能不完美,如"已发货后无法回滚"),不是数据库 rollback
- "中间状态应该对外隐藏"——错。Saga 明确接受中间状态可见——业务要设计成"中间状态也能合理"
- "Choreographed Saga = 简单"——错。Choreographed Saga 难追踪、难测试,简单的标准是 Orchestrated
- "Saga 不需要幂等"——大错。补偿可能重试,幂等是 Saga 的硬要求
- "Saga 能保证一致性"——半对。Saga 保证最终业务一致,不保证任意时刻都一致
关键洞察
Saga = 承认分布式系统的现实,用业务补偿代替技术锁。
2PC 想"既要跨服务原子性" = 现实不允许。 Saga 选"放弃强一致,换工程可用" = 业务上能接受。 这不是工程妥协,是工程智慧。
V2 B6 权衡的典型案例—— Saga 的代价 = 中间状态可见 + 补偿可能失败 + 业务设计变复杂。 Saga 的收益 = 高可用 + 高性能 + 跨服务事务可行。
Saga 设计的 3 个核心问题:
- 每个子事务真的有补偿吗?(不是所有业务都有补偿)
- 补偿幂等吗?(重试不能双倍)
- 中间状态对外业务合理吗?(用户看到"已下单未付款"是正常的)
判断 Saga 用得对不对的标志:
- 补偿可幂等
- 失败可恢复(不需人工介入)
- 中间状态对外业务可接受
- Saga 状态可查询 / 可恢复(长 Saga 关键)
相关链接
- CQRS + Event Sourcing——Saga 的状态持久化机制
- Event Driven Workflow——Choreography vs Orchestration
- Compound Control——前馈 + 反馈的协调控制
- [V3 涌现作为] · 分解——Saga 是 BC 分解后的协调机制
- [V3 涌现作为] · 权衡——Saga = 分布式三难困境的解
- [V3 涌现作为] · 反馈——Saga 补偿 = 反馈环的修正动作