高级 #distributed#transactions#saga

分布式事务(2PC, Saga)

分布式事务跨越多个节点——2PC(两阶段提交)用协调者保证所有节点全提交或全回滚,但阻塞。Saga 把长事务拆分为子事务+补偿操作

💸 跨服务的”转账”——没那么简单

你在淘宝买了 200 元的书。这涉及:

  • 订单服务:创建订单(状态:待支付)
  • 库存服务:扣减库存(少了 1 本书)
  • 支付服务:扣款 200 元

如果订单创建完、库存扣了、但支付失败——你会得到一个”不能支付的订单,但书已经被扣了”的尴尬局面。

分布式事务就是在多个服务/数据库之间保证”要么全成功、要么全失败”——和单机数据库的 ACID 一样,但跨了多个节点。


📋 2PC——两阶段提交

2PC 引入一个”协调者”来协调所有参与者:

阶段 1:准备(Prepare)
协调者→参与者1: "能提交吗?" ← 锁定资源
协调者→参与者2: "能提交吗?" ← 锁定资源
协调者→参与者3: "能提交吗?" ← 锁定资源

阶段 2:提交(Commit)/ 中止(Abort)
如果全部回复 OK → 协调者→全部:COMMIT!
如果有任一个回复 NO → 协调者→全部:ROLLBACK!

问题

  • 阻塞:阶段 1 锁定了资源——如果有人一直不回复,资源一直锁着
  • 协调者单点故障:协调者挂了——所有人都等着
  • 性能差:同步等待所有参与者回复

🏪 **类比:全班去郊游”

班长(协调者)组织全班去郊游:

阶段 1:一个一个问”周六你有空吗?“——大家说”有”(但实际还没出发,只是确认)

阶段 2:如果全班都”有”→“好,周六早上 8 点集合!“(COMMIT) 如果有一个人说”没空”→“算了,不去了”(ROLLBACK)

问题:大家在阶段 1 都预留了时间(锁资源)——如果有人一直不回话,所有人的周六都空着(阻塞)。


⛓️ Saga——用”补偿”代替”锁”

2PC 的”锁”太重量级了——Saga 的思路是:不锁,但万一错了能”补偿”。

Saga = 把一个大事务拆成多个子事务 (T₁, T₂, T₃...)
每个子事务有一个"补偿操作" (C₁, C₂, C₃...)

正常流程:
T₁(扣库存)→ T₂(扣钱)→ T₃(创建订单)→ ✅ 完成

如果 T₃ 失败:
T₁(扣库存)→ T₂(扣钱)→ ❌ T₃(创建订单失败)
                  → C₂(退款)→ C₁(加回库存)
                  ← 补偿 ←
# Saga 模式——下订单
def place_order():
    try:
        inventory.decrease(item_id, 1)          # T₁:扣库存
        payment.charge(user_id, amount)          # T₂:扣钱
        order.create(user_id, item_id, amount)   # T₃:创建订单
    except Exception:
        # 补偿:反向操作
        order.delete(user_id, order_id)          # C₃
        payment.refund(user_id, amount)          # C₂
        inventory.increase(item_id, 1)           # C₁

Saga 的特点

  • 最终一致性——中间状态是不一致的,但最终会一致
  • 没有锁——性能好
  • 应用层实现补偿逻辑——比 2PC 复杂,但更灵活

⚖️ 2PC vs Saga

对比2PCSaga
一致性强一致(ACID)最终一致(BASE)
性能慢(同步等待+锁)快(无锁)
适用场景短事务、低并发长事务、高并发
实现复杂度依赖事务中间件应用层实现补偿
典型应用XA 协议、Seata AT微服务、电商订单

📝 小结

概念一句话
2PC两阶段提交——准备+提交——强一致但阻塞
Saga拆分子事务+补偿操作——最终一直但高性能
TCCTry-Confirm-Cancel——另一种补偿模式(支付场景常用)
核心权衡强一致←→最终一致,低性能←→高性能
选择依据需要强一致选 2PC,能接受最终一致选 Saga

🎯 思考题:你在设计一个”跨行转账”系统——用户从建行转 1000 元到工行。两个银行各自有独立数据库。你会用 2PC 还是 Saga?为什么?

为什么先学这个? 分布式系统板块到此全部结束!从 CAP 理论到分布式事务——这是一个从”为什么”到”怎么做”的完整旅程。接下来可以进入量子计算板块继续学习。