分布式事务
解释一个业务动作跨数据库、服务或消息系统后,为何单库 ACID 不再覆盖全部副作用,并对比 2PC、Saga、Transactional Outbox 与幂等补偿的适用边界。
[!info] related notes
分布式事务
[!abstract] 核心结论 分布式事务处理一个业务操作跨越多个独立事实源时的一致性。工程上先区分是否真的需要“所有参与者原子提交”,还是允许通过 Saga、Outbox、幂等和补偿达到可观察的最终一致。
问题从哪里出现
Order DB commit succeeds
→ Inventory call succeeds
→ Payment call times out
→ Notification not sent
单个本地数据库事务无法覆盖其他数据库、Broker 和第三方 API。失败时系统需要知道哪些步骤已提交、哪些结果未知、怎样继续或补偿。
先定义业务不变量
不要先问“用 2PC 还是 Saga”,先问:
- 哪些状态必须同时成立?
- 哪些步骤可延迟?
- 哪些副作用可补偿?
- 哪些外部系统支持幂等和查询状态?
- 用户能否看到 Pending / Processing?
- 最长不一致窗口是多少?
很多需求可以通过重新划分 Aggregate / 服务边界,回到一个本地事务中解决。
Two-Phase Commit(2PC)
Phase 1: Prepare
Coordinator asks all participants to prepare
Phase 2: Commit / Abort
If all prepared → commit
Else → abort
优点:接近跨资源原子提交。
代价:
- Coordinator 与 Participant 状态复杂;
- Prepared Resource 可能长期占锁;
- 故障恢复和 Heuristic Outcome 困难;
- 延迟与可用性受最慢参与者影响;
- 外部 HTTP API 通常不支持 XA / Prepare。
适合参与者可控、强原子性确有必要且规模有限的场景。
Saga
Saga 将长事务拆成一系列本地事务,每步成功后发布下一步;失败时执行补偿:
Create Order
→ Reserve Inventory
→ Authorize Payment
→ Confirm Order
failure at payment
→ Release Inventory
→ Cancel Order
补偿不是数据库 Rollback:已经发出的邮件、外部扣款或用户观察可能无法完全撤销。Saga 需要显式状态机、幂等、重试、超时和人工介入。
Choreography 与 Orchestration
- Choreography:服务通过事件自行响应,耦合分散但整体流程难观察;
- Orchestration:中央协调器发命令并记录状态,流程清晰但协调器成为关键组件。
Transactional Outbox
解决“本地数据库提交 + 发布消息”的双写:
single DB transaction:
update business tables
insert outbox event
commit
publisher / CDC
→ read outbox
→ publish to broker
→ mark / checkpoint
Outbox 让业务状态和“待发布事实”同事务提交,但仍可能重复发布,因此 Consumer 必须幂等。
Inbox / Processed Message
Consumer 在自己的本地事务里:
insert message_id unique
+ update business state
commit
重复投递时唯一约束阻止重复副作用。Inbox 与 Outbox 组合常用于最终一致集成。
TCC / Reservation
Try–Confirm–Cancel 先预留资源,再确认或取消:
Try: reserve inventory / funds
Confirm: finalize
Cancel: release reservation
它要求参与者专门实现三套业务接口和超时清理,适合资源预留语义明确的高价值流程。
Unknown Outcome
外部调用 Timeout 时不能直接假设失败:
payment request sent
→ provider charges successfully
→ response lost
需要稳定 Idempotency Key、查询接口和 Reconciliation。生成新 Key 再重试可能重复扣款。
最终一致不等于无限等待
应定义:
- SLA / Maximum Convergence Time;
- Retry Budget;
- Dead Letter / Manual Review;
- User-visible Pending State;
- Reconciliation Job;
- Compensation Failure Handling。
没有收敛目标和修复路径的“最终一致”只是把错误拖延。
设计顺序
1. Define invariant and source of truth
2. Reduce cross-boundary writes if possible
3. Choose local transaction boundaries
4. Add idempotency and status query
5. Choose 2PC / Saga / TCC / Outbox
6. Persist workflow state and attempts
7. Add timeout, retry, compensation, reconciliation
8. Observe business completion, not just message ACK
设计检查表
- 每一步的本地提交点和 Source of Truth 明确。
- Unknown Outcome 有查询和 Reconciliation。
- Command / Message 使用稳定 Idempotency Key。
- Compensation 的业务含义与失败处理明确。
- Outbox Publisher 和 Consumer 都允许重复。
- Pending / Failed / Manual Review 状态对用户可解释。
- Trace 能还原完整 Saga / Transaction Timeline。
- 选择 2PC 前已证明强原子性和参与者能力确实需要。