分布式事务与一致性模型深度实战:从 2PC 到 Saga 的工程决策
1. 引言:为什么分布式事务如此之难?
在单体数据库时代,事务的 ACID 特性由数据库引擎天然保障。一旦系统演进到微服务架构,一次业务操作可能横跨多个数据库、消息队列、第三方服务,传统的事务机制便力不从心。分布式事务的核心挑战在于:网络不可靠、节点可能故障、时钟不同步。
本文将从理论出发,深入剖析 2PC、3PC、TCC、Saga、事件溯源、最终一致性等主流方案,并给出工程实践中的选型决策和避坑指南。
2. CAP 定理与一致性光谱
Brewer 的 CAP 定理指出,在一个分布式系统中,一致性 (Consistency)、可用性 (Availability)、分区容忍性 (Partition tolerance) 三者不可兼得,最多只能同时满足其中两项。
- CP 系统:保证一致性和分区容忍性,但在网络分区时可能拒绝服务(如 ZooKeeper、etcd)
- AP 系统:保证可用性和分区容忍性,但可能返回过时数据(如 Cassandra、DynamoDB)
- CA 系统:传统关系型数据库,无法真正容忍网络分区
实际工程中,一致性是一个光谱而非二元选择:
| 一致性级别 | 描述 | 典型用例 |
|---|---|---|
| 强一致性 | 写入后立即可读 | 库存扣减、转账 |
| 线性一致性 | 所有操作按全局实时顺序 | 分布式锁、Leader 选举 |
| 顺序一致性 | 所有节点看到相同操作顺序 | Replication 协议 |
| 因果一致性 | 因果相关操作有序 | 评论回复、消息 |
| 最终一致性 | 若无新写入,最终一致 | DNS、推荐系统 |
3. 两阶段提交 (2PC)
3.1 协议流程
2PC 是最经典的分布式事务协议,将事务分为两个阶段:
阶段一:投票阶段 (Voting Phase)
- 协调者向所有参与者发送 Prepare 请求
- 参与者执行事务操作(写入 undo/redo log),但不提交
- 参与者返回 Yes(成功)或 No(失败)
阶段二:提交阶段 (Commit Phase)
- 若所有参与者返回 Yes,协调者发送 Commit 指令
- 若有任何参与者返回 No,协调者发送 Rollback 指令
- 参与者执行提交或回滚,返回 ACK
3.2 故障模式分析
- 参与者故障:协调者超时未收到回复,回滚事务
- 协调者故障(阶段一):新协调者回滚事务(保守策略)
- 协调者故障(阶段二):参与者处于 Prepared 状态,阻塞等待——这是 2PC 最大的问题
- 网络分区:可能导致部分提交部分回滚的不一致
3.3 工程缺陷
2PC 的主要问题在于同步阻塞和单点故障:
- 参与者在 Prepare 阶段后持有锁,若协调者故障则长时间阻塞
- 协调者是单点,故障后整个系统停滞
- 两阶段之间的延迟导致事务吞吐量受限
4. 三阶段提交 (3PC)
3PC 试图解决 2PC 的阻塞问题,将流程拆分为三个阶段:
- CanCommit:协调者询问参与者是否可以执行(不持锁)
- PreCommit:参与者执行操作并写日志
- DoCommit:参与者提交事务
3PC 引入了超时机制:参与者在 PreCommit 后若未收到 DoCommit,会默认提交而非阻塞。但这带来了网络分区下的不一致风险:当协调者与参与者网络断开时,参与者自行提交,若协调者回滚,则产生脑裂。
实际上 3PC 并未被广泛采用,其复杂度增加而收益有限,多数工程实践选择其他方案。
5. TCC (Try-Confirm-Cancel)
5.1 核心思想
TCC 是一种补偿型分布式事务方案,将每个参与者的业务操作拆分为三个方法:
- Try:预留资源(如冻结库存、预扣余额)
- Confirm:确认执行(如实际扣减库存、扣款)
- Cancel:取消执行(如释放冻结库存、退还预扣余额)
5.2 执行流程
- 主业务方调用所有参与者的 Try 方法
- 若所有 Try 成功,依次调用 Confirm
- 若任一 Try 失败,对已成功的参与者调用 Cancel
5.3 工程陷阱
空回滚问题:Try 阶段超时,协调者发起 Cancel,但参与者尚未执行 Try。Cancel 方法需要能正确处理"还没有 Try 就收到 Cancel"的情况,正确做法是识别并跳过空回滚。
悬挂问题:Try 阶段因网络延迟晚于 Cancel 到达。参与者 Cancel 后收到 Try,导致资源被不当占用。解决方案:记录 Cancel 已执行,迟到 Try 直接拒绝。
幂等性:Confirm 和 Cancel 可能被重复调用(网络重试、幂等重试),必须保证多次调用结果一致。通常使用事务状态表实现幂等控制。
5.4 适用场景
TCC 适合业务可补偿、资源预留明确的场景,如电商下单(库存冻结→扣减→释放)、金融转账(预扣→划拨→退还)。
6. Saga 模式
6.1 核心思想
Saga 将一个长事务拆分为多个本地短事务,每个短事务有对应的补偿操作。若某步骤失败,按逆序执行补偿操作回滚。
编排式 Saga (Choreography):无中心协调者,每个服务完成后发布事件,下游服务监听事件并执行操作。
命令式 Saga (Orchestration):由 Saga Orchestrator 集中管理事务流程,依次调用各服务的操作,失败时调用补偿。
6.2 编排式 vs 命令式
| 维度 | 编排式 Choreography | 命令式 Orchestration |
|---|---|---|
| 复杂度 | 去中心化,逻辑分散 | 集中管理,职责清晰 |
| 耦合度 | 服务间通过事件耦合 | 依赖 Orchestrator |
| 可观测性 | 难以追踪全程状态 | Orchestrator 掌握完整状态 |
| 适用场景 | 简单工作流、步骤少 | 复杂业务流程、需状态管理 |
6.3 Saga 的隔离性缺陷
Saga 缺乏隔离性——每个步骤提交后,其他事务就能看到中间状态。解决方案包括:
- 语义锁 (Semantic Lock):在业务层面标记数据状态(如 "pending"),其他事务需检查状态
- 交换式更新 (Commutative Updates):设计可交换的操作顺序
- 乐观锁:通过版本号检测并发冲突
7. 事件溯源 (Event Sourcing)
事件溯源是一种架构模式:不存储实体的当前状态,而是存储导致状态变化的所有事件序列。当前状态通过重放事件重建。
事件溯源与分布式事务
在分布式事务场景中,事件溯源常与 Saga 结合:
- 服务执行本地事务并发布领域事件
- 下游服务订阅事件并执行操作
- 失败时发布补偿事件触发上游补偿
Event Sourcing CQRS 的组合在现代分布式系统中越来越流行,如 Axon Framework、EventStoreDB。
8. 最终一致性模式
8.1 可靠事件队列
核心思路:将分布式事务拆解为本地事务 消息发送,通过消息队列实现跨服务通信。
实现方案:本地消息表
- 业务执行和消息插入在同一本地事务中
- 独立的消息投递服务轮询消息表并投递到 MQ
- 下游消费消息并执行业务逻辑
- 通过重试保证消息最终被消费
8.2 事务消息 (RocketMQ 模式)
RocketMQ 的事务消息将消息发送分为两个阶段:
- 发送 Half Message(对消费者不可见)
- 执行本地事务,提交或回滚 Half Message
- MQ 回查本地事务状态(防止执行过程中崩溃)
8.3 最大努力通知
适用于对一致性要求不严格的场景:
- 执行方尽力向通知方发送结果通知
- 通知方可主动查询执行方的状态
- 允许失败,但提供查询和补偿机制
9. 选型决策矩阵
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强 | 低 | 低 | 跨库事务、强一致、容忍阻塞 |
| TCC | 强(业务层) | 中 | 高 | 电商、金融、可补偿业务 |
| Saga | 最终 | 高 | 中 | 长流程、微服务编排 |
| 事件溯源 事件驱动 | 最终 | 高 | 高 | 复杂业务、审计需求 |
| 可靠事件队列 | 最终 | 高 | 中 | 异步通知、弱一致容忍 |
| 本地消息表 | 最终 | 高 | 低 | 简单跨服务调用 |
10. 工程实战:避坑指南
10.1 幂等性设计
任何分布式事务方案都必须处理重复请求。核心手段:
- 全局唯一 ID(雪花 UUID)去重
- 数据库唯一约束(业务键 事务 ID)
- Redis SetNX 分布式防重
- 状态机 乐观锁(CAS 更新)
10.2 超时与重试策略
- 设置合理的超时时间(参考 P99 延迟)
- 指数退避重试 抖动(避免 thundering herd)
- 重试上限与熔断机制
- 区分可重试错误和不可重试错误
10.3 监控与告警
- 事务成功率、超时率、回滚率
- 各阶段耗时分布(P50/P95/P99)
- 悬挂事务检测与自动恢复
- 定时对账(检查数据一致性)
10.4 数据对账
无论采用何种方案,数据对账都是最后一道防线:
- 定时扫描事务状态表,找出未完成的事务
- 与上下游系统比对数据快照
- 提供自助修复工具和手动补偿流程
11. 实战案例:电商下单的分布式事务实现
考虑一个电商下单场景:用户下单涉及库存扣减、积分增加、优惠券核销、订单创建四个服务。
Saga 实现方案:
- 订单服务:创建订单(状态=PENDING),发布 OrderCreated 事件
- 库存服务:扣减库存,发布 StockReserved 事件;释放库存补偿
- 优惠券服务:核销优惠券,发布 CouponUsed 事件;恢复优惠券补偿
- 积分服务:增加积分,发布 PointsAdded 事件;扣减积分补偿
若积分服务增加失败,Saga Orchestrator 发起补偿:
- 恢复优惠券(Cancel CouponUsed)
- 释放库存(Cancel StockReserved)
- 更新订单状态为 FAILED
12. 前沿趋势
- Percolator 模型 (TiDB):基于 MVCC 分布式锁的乐观事务实现
- Calvin 模型 (FaunaDB):确定性事务调度,所有副本按相同顺序执行
- 混合逻辑时钟 (HLC):无需全局时钟的因果一致性方案
- Serverless 事务:云原生架构下的事务编排(AWS Step Functions、Azure Durable Functions)
13. 总结
分布式事务没有银弹,选型的核心原则是:
- 能不用就不用:通过业务设计避免分布式事务(如合并服务、数据冗余)
- 能最终一致就最终一致:大多数业务场景容忍短暂不一致
- 能补偿就不锁定:Saga/TCC 比 2PC 更适合高并发场景
- 必须有兜底:数据对账是分布式事务的最后防线
理解每种方案的权衡(trade-off),根据业务特征做出合理的工程取舍,才是分布式事务实战的精髓。

发表评论 取消回复