专栏文章
专栏文章
分布式系统系列
1. 分布式系统 #01:分布式一致性 2. 分布式系统 #02:分布式事务 3. 分布式系统 #03:分布式协调与选举 4. 分布式系统 #04:分布式系统设计模式 5. 分布式系统 #05:金融级分布式系统实践

分布式系统 #02:分布式事务

发布于 2026-05-28 14:04 👁 7 次阅读
#transaction#分布式

本文系统梳理分布式事务的核心难点与解决方案:从 2PC/3PC 的强一致性方案,到 TCC/Saga 的业务层方案,再到本地消息表和事务消息的最终一致性方案。读完能清楚知道各方案的适用场景、核心问题与工程取舍。

相关文章分布式一致性 · 分布式协调与选举 · 分布式系统设计模式 · 金融级分布式系统实践


目录

章节 说明
分布式事务的难点 为什么单机事务方案在分布式下失效
2PC 两阶段提交 强一致性方案,协调者/参与者模型
3PC 三阶段提交 2PC 的改进,引入超时机制
TCC 业务层面的分布式事务,Try-Confirm-Cancel
Saga 长事务 正向事务 + 补偿事务
本地消息表 最终一致性,基于数据库
事务消息 最终一致性,基于消息队列
方案对比 各方案的核心取舍

分布式事务的难点

本地事务(单机)具备 ACID 四个特性:

特性 含义
A(原子性) 要么全部执行,要么全部不执行
C(一致性) 事务前后数据满足完整性约束
I(隔离性) 并发事务互不干扰
D(持久性) 提交后永久保存,崩溃后可恢复

分布式事务是由多个本地事务组合而成,跨越多台机器甚至多个数据中心。难点在于:

  1. 网络不可靠:消息可能丢失、延迟、重复
  2. 节点可能宕机:任意节点在任意时刻都可能故障
  3. 无全局时钟:无法精确判断事件的先后顺序
  4. 性能 vs 一致性:保证强一致性需要多轮协商,严重影响吞吐量

典型场景:电商下单 = 订单系统创建订单 + 库存系统减库存。两个操作在不同服务器上,必须同时成功或同时失败(All or Nothing)。


2PC 两阶段提交

基于 XA 协议,通过协调者统一管理所有参与者,实现强一致性。

dist tx 2pc

角色

两个阶段

sequenceDiagram
    participant C as 协调者
    participant P1 as 参与者-1(订单)
    participant P2 as 参与者-2(库存)
    Note over C,P2: 阶段一:投票(Voting)
    C->>P1: CanCommit?
    C->>P2: CanCommit?
    P1-->>C: Yes(执行操作,记录日志,但不提交)
    P2-->>C: No(库存不足)
    Note over C,P2: 阶段二:提交(Commit)—— 有 No 则回滚
    C->>P1: DoAbort
    C->>P2: DoAbort
    P1-->>C: HaveCommitted(已回滚)
    P2-->>C: HaveCommitted

成功路径:所有参与者返回 Yes → 协调者发送 DoCommit → 所有参与者提交并释放资源。

失败路径:任意参与者返回 No → 协调者发送 DoAbort → 所有参与者回滚。

三个核心问题

问题 描述 影响
同步阻塞 参与者持有临界资源锁等待协调者指令,其他请求被阻塞 不支持高并发
单点故障 协调者宕机后,参与者永久等待,整个集群停滞 可用性低
数据不一致 提交阶段网络异常,部分参与者收到 DoCommit 而部分未收到 数据不一致

3PC 三阶段提交

对 2PC 的改进,引入超时机制准备阶段,减少同步阻塞,但无法完全解决数据不一致。

三个阶段

sequenceDiagram
    participant C as 协调者
    participant P as 参与者
    Note over C,P: 阶段一:CanCommit(询问,不加锁)
    C->>P: CanCommit?
    P-->>C: Yes/No
    Note over C,P: 阶段二:PreCommit(预提交,加锁)
    C->>P: PreCommit(所有人都 Yes 时)
    P-->>C: ACK(执行操作,记录 Undo/Redo 日志)
    Note over C,P: 阶段三:DoCommit(正式提交)
    C->>P: DoCommit
    P-->>C: ACK(提交,释放资源)

2PC vs 3PC 对比

维度 2PC 3PC
阶段数 2 3
超时机制 仅协调者有 协调者和参与者都有
阻塞问题 严重 有所改善
数据不一致 存在 仍然存在(PreCommit 阶段网络分区)
复杂度

关键改进:参与者在 PreCommit 后超时未收到 DoCommit,默认提交(而非等待),减少阻塞。但这也可能在网络分区时导致数据不一致。


TCC

TCC(Try-Confirm-Cancel)是业务层面的分布式事务协议,不依赖数据库锁,适合跨服务、跨数据库的复杂业务场景。

三个操作

操作 含义 对应 2PC
Try 预留资源,锁定业务数据,确保 Confirm 一定能成功 提交请求阶段
Confirm 正式执行业务操作,使用 Try 预留的资源 提交执行阶段
Cancel 撤销 Try 阶段的预留,释放资源 回滚

订票系统示例

场景:深圳→上海(深圳航空)+ 上海→北京(上海航空),两段都订成功才算成功。

sequenceDiagram
    participant S as 订票系统
    participant CA as 深圳航空
    participant CB as 上海航空
    Note over S,CB: Try 阶段:预留机票
    S->>CA: 预留深圳→上海机票
    S->>CB: 预留上海→北京机票
    CA-->>S: 预留成功
    CB-->>S: 预留成功
    Note over S,CB: Confirm 阶段:确认订购
    S->>CA: 确认订购
    S->>CB: 确认订购
    CA-->>S: 订购成功
    CB-->>S: 订购成功

若 Try 阶段任一失败,则执行 Cancel,撤销已预留的资源。

TCC 的核心要点

  1. 业务层面,非数据库层面:每个操作对数据库来说是一个本地事务,操作完成后立即释放数据库资源,避免长时间锁定
  2. 跨服务、跨数据库:可以将多个服务的操作组合为一个原子操作
  3. Confirm/Cancel 必须幂等:因为这两个操作可能因网络问题重试
  4. 业务侵入性强:需要为每个业务操作设计 Try/Confirm/Cancel 三个接口

TCC 的陷阱

陷阱 描述 解决方案
空回滚 Try 未执行,Cancel 被调用 Cancel 中检查 Try 是否执行,未执行则直接返回成功
悬挂 Cancel 先于 Try 执行 记录事务状态,Cancel 后拒绝 Try 的执行
幂等 Confirm/Cancel 被重复调用 通过唯一事务 ID 做幂等控制

Saga 长事务

Saga 将一个长事务拆分为一系列本地事务,每个本地事务有对应的补偿事务,适合长时间运行的业务流程。

dist tx saga

核心思想

T1 → T2 → T3 → ... → Tn     (正向事务链)
C1 ← C2 ← C3 ← ... ← Cn     (补偿事务链,逆序执行)

两种协调模式

模式 说明 适用场景
编排(Choreography) 每个服务完成后发布事件,下一个服务监听并执行 简单流程,服务少
指挥(Orchestration) 中央协调者(Saga Orchestrator)指挥每个服务执行 复杂流程,易于监控

Saga vs TCC

维度 TCC Saga
隔离性 较好(Try 阶段预留资源) 较差(中间状态可见)
业务侵入 高(需实现 3 个接口) 中(需实现补偿事务)
适用场景 短事务,需要较强隔离 长事务,可接受中间状态
典型应用 支付、库存扣减 旅行预订(机票+酒店+租车)

本地消息表

将消息持久化到本地数据库,利用数据库事务保证消息的可靠投递,实现最终一致性。

流程

sequenceDiagram
    participant A as 服务A(订单)
    participant DB as 本地数据库
    participant MQ as 消息队列
    participant B as 服务B(库存)
    Note over A,DB: 原子操作:业务操作 + 写消息表
    A->>DB: BEGIN TRANSACTION
    A->>DB: 创建订单
    A->>DB: 插入消息表(状态=待发送)
    A->>DB: COMMIT
    Note over A,MQ: 定时任务扫描消息表
    A->>MQ: 发送消息
    MQ-->>A: 发送成功
    A->>DB: 更新消息状态=已发送
    MQ->>B: 投递消息
    B-->>MQ: 消费成功(ACK)

关键点

优缺点

维度 说明
优点 实现简单,利用现有数据库能力
缺点 消息表与业务库耦合,数据量大时性能压力大
适用 系统规模不大,已有关系型数据库

事务消息

利用消息队列的事务消息特性(如 RocketMQ),将消息发送与本地事务绑定,实现最终一致性。

RocketMQ 事务消息流程

sequenceDiagram
    participant P as 生产者(订单服务)
    participant MQ as RocketMQ Broker
    participant C as 消费者(库存服务)
    P->>MQ: 发送半消息(Half Message)
    MQ-->>P: 发送成功
    P->>P: 执行本地事务(创建订单)
    alt 本地事务成功
        P->>MQ: Commit(消息可投递)
        MQ->>C: 投递消息
        C-->>MQ: ACK
    else 本地事务失败
        P->>MQ: Rollback(消息丢弃)
    else 本地事务状态未知(超时)
        MQ->>P: 回查事务状态
        P-->>MQ: 返回 Commit/Rollback
    end

半消息(Half Message):对消费者不可见,只有 Commit 后才能被消费。

RocketMQ vs Kafka 事务对比

维度 RocketMQ 事务消息 Kafka 事务
设计目标 生产者本地事务 + 消息投递的原子性 Producer 跨多个分区的原子写入
回查机制 有(Broker 主动回查生产者)
消费者幂等 需要业务自行保证 需要业务自行保证
适用场景 分布式事务最终一致性 流处理 Exactly-Once 语义

方案对比

方案 一致性 性能 业务侵入 适用场景
2PC 强一致 低(同步阻塞) 低(数据库支持) 并发低、强一致要求高
3PC 强一致(有缺陷) 较低 2PC 的改进,实际少用
TCC 最终一致(业务层强一致) 高(需实现 3 接口) 跨服务短事务,需较强隔离
Saga 最终一致 中(需实现补偿) 长事务,可接受中间状态
本地消息表 最终一致 中(需维护消息表) 规模适中,已有关系型数据库
事务消息 最终一致 低(MQ 原生支持) 异步场景,消息队列已在使用

选型原则

  • 涉及钱、强隔离要求 → TCC 或 2PC
  • 长业务流程、可接受中间状态 → Saga
  • 异步场景、已用消息队列 → 事务消息
  • 简单场景、已有关系型数据库 → 本地消息表
  • 避免在高并发系统中使用 2PC(同步阻塞是性能杀手)

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论