Skip to content

分布式事务深度解析:从理论到选型实战

引言

微服务架构普及后,单体应用被拆分为数十甚至上百个独立服务,每个服务拥有自己的数据库。一个看似简单的电商下单流程,实际上要跨越商品、库存、订单、支付、物流多个服务和数据源。

text
用户下单 → 扣减库存 → 创建订单 → 支付扣款 → 发送物流通知
   │           │          │          │            │
   ▼           ▼          ▼          ▼            ▼
商品服务    库存服务    订单服务    支付服务    物流服务
  (MySQL)   (Redis)    (MySQL)    (MySQL)    (RocketMQ)

任何一个环节失败,都可能导致库存扣了但订单没创建、钱扣了但库存没扣等严重数据不一致问题。分布式事务就是解决这类跨服务、跨数据源一致性问题的关键技术。

本文从核心理论出发,逐一拆解主流分布式事务方案的实现原理、内部机制和适用场景,并在最后给出完整的选型决策指南。

一、核心理论基础

1.1 CAP 定理

分布式系统无法同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance),最多只能同时满足其中两个。

类型牺牲保证代表
CP可用性强一致性ZooKeeper、etcd、TiDB
AP强一致性可用性Cassandra、Eureka、大多数 NoSQL

CAP 定理的意义在于,它让你在做架构设计时明确一个前提:分区容错性通常不可牺牲,所以实际选择往往是在 C 和 A 之间做权衡。

1.2 BASE 理论

AP 系统的实践指导:

概念含义说明
Basically Available(基本可用)系统出现故障时,允许损失部分可用性响应时间变长、部分功能降级
Soft state(软状态)允许中间状态存在数据在同步过程中可以暂时不一致
Eventually consistent(最终一致)不保证实时一致,但保证最终一致在一定时间窗口后,所有副本数据收敛一致

BASE 不是对 ACID 的否定,而是对大规模分布式系统在可用性优先场景下的补充思路。

1.3 一致性模型光谱

text
┌─────────────────────────────────────────────────────────────┐
│  强一致性 ──────▶ 顺序一致性 ──────▶ 因果一致性 ──────▶ 最终一致性  │
│  (Linearizable)   (Sequential)      (Causal)        (Eventual) │
│                                                               │
│  ZooKeeper       Kafka             CockroachDB      Redis     │
│  etcd            (默认配置)         (默认配置)       主从复制   │
│  TiDB(悲观锁)                                                      │
└─────────────────────────────────────────────────────────────┘

理解这个光谱的用途是:不要在不必要的地方追求强一致性。很多业务场景下,最终一致性不仅够用,还能换来显著的可用性和性能提升。

二、两阶段提交(2PC)— 经典强一致方案

2.1 基本原理

2PC 通过协调者(Transaction Manager,TM)统一管理各参与者(Resource Manager,RM)的事务状态。

text
阶段一(投票):
  TM ──▶ 询问所有 RM:"你能执行吗?"
  RM ──▶ 执行本地事务(不提交),返回 Yes/No

阶段二(决策):
  全部 Yes  → TM 发送 Commit 指令 → 各 RM 正式提交
  任一 No   → TM 发送 Rollback 指令 → 各 RM 回滚

2.2 XA 协议详解

XA(eXtended Architecture)是 X/Open 组织制定的分布式事务标准协议,它定义了事务管理器(TM)和资源管理器(RM)之间的接口规范,让多个异构数据库可以在同一个全局事务中协同工作。

为什么需要 XA

在没有 XA 之前,如果你要跨 MySQL 和 Oracle 做一个全局事务,每个数据库都有自己的事务 API,协调者需要为每种数据库写不同的交互逻辑。XA 把这套交互统一成了标准接口,任何支持 XA 的数据库或中间件,都能被同一个 TM 管理。

核心角色

角色缩写职责
事务管理器TM(Transaction Manager)协调全局事务,驱动两阶段提交,决定最终提交或回滚
资源管理器RM(Resource Manager)管理具体资源的事务状态,如 MySQL、Oracle、PostgreSQL、消息队列等

一个全局事务可能跨越多个异构 RM,XA 协议就是 TM 和这些 RM 之间沟通的标准语言。Java 中的 javax.transaction.xa.XAResource 接口就是 XA 规范的 Java 映射。

XA 与 2PC 的关系

很多人把 XA 和 2PC 混为一谈,实际上:

  • 2PC 是一种算法/协议模式,描述的是"先投票再决策"的两阶段协作思路
  • XA 是一个具体的标准接口规范,它规定了 TM 和 RM 之间交互的 API(如 xa_startxa_endxa_preparexa_commit

2PC 可以用 XA 实现,也可以用非 XA 方式实现。Seata 的 XA 模式就是基于 XA 标准接口封装的 2PC 实现。

2.3 代表实现

中间件实现方式特点
MySQL XA数据库原生支持InnoDB 存储引擎实现,Redo Log + Binlog 双写保证崩溃恢复
Seata XA 模式框架层封装对业务零侵入,自动管理 XA 会话
AtomikosJava 开源 TM老牌 XA 事务管理器,适合传统单体应用

2.4 MySQL XA 内部机制

sql
XA START 'xid-123';      -- 开启事务分支
UPDATE inventory SET ...; -- 执行业务 SQL
XA END 'xid-123';         -- 结束事务分支
XA PREPARE 'xid-123';     -- 关键:刷盘 Redo Log,标记 PREPARE,释放表锁但保留行锁
XA COMMIT 'xid-123';      -- 正式提交(崩溃恢复时,PREPARE + Binlog 存在则提交)

XA PREPARE 是关键步骤:此时 Redo Log 已刷盘,行锁仍持有,其他事务无法修改被锁数据。这也是为什么 2PC 在高并发下吞吐量急剧下降的根本原因。

2.5 核心痛点

痛点原因影响
同步阻塞Prepare 阶段 RM 持有锁但不提交其他事务无法修改数据,并发度受限
单点故障协调者宕机可能导致参与者一直阻塞需要超时回滚机制,但实现复杂
性能瓶颈网络往返 2N+1 次(N 为参与者数)高并发下延迟和吞吐量急剧下降

2PC 适合短事务、低并发、对一致性要求极高的场景,如传统金融核心系统的跨库转账。

三、TCC(Try-Confirm-Cancel)— 高性能业务补偿

3.1 核心思想

将每个业务操作拆分为三个阶段,通过业务逻辑保证最终一致。

阶段职责库存扣减示例
Try资源检查与预留检查库存 → Redis 预占库存 → 生成预扣减记录
Confirm正式提交将预扣减转为正式扣减,完成业务
Cancel释放预留释放 Redis 预占,删除预扣减记录

3.2 Seata TCC 模式架构

text
业务应用                    Seata Server (TC)              各服务 RM
    │                            │                            │
    │  @GlobalTransactional       │                            │
    │ ─────────────────────────▶ │                            │
    │                            │  注册全局事务               │
    │  Try 阶段                   │                            │
    │ ──────────────────────────────────────────────────────▶ │
    │  各服务预留资源              │                            │
    │                            │  全局决策                   │
    │  Confirm / Cancel           │                            │
    │ ◀────────────────────────────────────────────────────── │

3.3 关键挑战与解决方案

问题原因解决方案
幂等性网络超时导致 Confirm/Cancel 重复执行分布式锁 + 事务日志表,记录已处理状态
悬挂Cancel 先于 Try 到达(网络延迟)记录空回滚状态,后续 Try 直接返回失败
空回滚Try 未执行或失败,Cancel 被调用判断事务日志,无 Try 记录则做空操作

3.4 适用场景

  • 电商秒杀:库存预占 + 超时释放
  • 高并发库存扣减:Redis 预占 + MySQL 确认
  • 金融核心交易:需强一致但 2PC 性能不足时

TCC 的核心代价是业务侵入性高:每个操作都要手写 Try、Confirm、Cancel 三个方法,开发和维护成本显著高于 2PC。

四、Saga 模式 — 长事务编排

4.1 核心思想

将长事务拆分为多个本地事务,每个本地事务有对应的补偿操作。正向成功则继续,失败则按相反顺序执行补偿。

text
正向流程:CreateOrder → DeductInventory → ProcessPayment
失败回滚:RefundPayment → RestoreInventory → CancelOrder

4.2 两种实现方式

类型说明代表
编排式(Choreography)各服务完成本地事务后发送事件,触发下一个服务事件驱动架构
协调式(Orchestration)由 Saga 协调器统一管理流程,决定下一步或补偿Seata Saga、Axon Framework

4.3 Seata Saga 状态机示例

json
{
  "startState": "CreateOrder",
  "states": {
    "CreateOrder": {
      "type": "serviceTask",
      "serviceName": "orderService.create",
      "next": "DeductInventory",
      "compensateState": "CancelOrder"
    },
    "DeductInventory": {
      "type": "serviceTask", 
      "serviceName": "inventoryService.deduct",
      "next": "ProcessPayment",
      "compensateState": "RestoreInventory"
    },
    "ProcessPayment": {
      "type": "serviceTask",
      "serviceName": "paymentService.process",
      "compensateState": "RefundPayment"
    }
  }
}

4.4 适用场景

跨多个服务的复杂业务流程,如旅游预订(机票→酒店→租车→保险)。Saga 支持异步执行和可视化监控,但补偿逻辑的设计是核心难点——不是每个业务操作都有语义上合理的"回退"操作。

五、可靠消息 — 最终一致性的工程实践

5.1 RocketMQ 事务消息

核心设计:通过半消息 + 二次确认 + 事务回查实现业务与消息的原子性。

text
┌─────────────┐      ┌─────────────────────────┐      ┌─────────────┐
│  Producer   │      │         Broker            │      │  Consumer   │
│             │      │ RMQ_SYS_TRANS_HALF_TOPIC  │      │             │
│             │      │ RMQ_SYS_TRANS_OP_TOPIC    │      │             │
└─────────────┘      └─────────────────────────┘      └─────────────┘

流程:
1. Producer 发送半消息 → Broker 替换 Topic 为 HALF_TOPIC(对消费者不可见)
2. Producer 执行本地事务(如 MySQL 订单创建)
3. Producer 二次确认:
   - COMMIT → Broker 恢复 Real Topic,投递到目标队列
   - ROLLBACK → 逻辑删除半消息
   - UNKNOWN → 等待回查
4. Broker 定时任务(60s)扫描未确认消息,主动回查 Producer
5. Producer 根据本地事务状态(查数据库)返回最终结果

关键内部机制

机制作用
Topic 替换半消息存储在内部 Topic,物理隔离,保证消费者不可见
OP Topic记录 Commit/Rollback 操作,用于与 Half Topic 对比判断是否需要回查
定时回查默认 60s 间隔,最多回查 15 次,超过则转入死信队列

5.2 Kafka 事务消息

核心设计:基于幂等生产者 + 事务协调器(TC)实现跨分区原子写入。

text
┌─────────────┐      ┌─────────────────────────┐      ┌─────────────┐
│  Producer   │      │ Transaction Coordinator │      │ Partitions  │
│(transaction│      │ __transaction_state     │      │             │
│   .id)      │      │ (内部 Topic,50 分区)     │      │             │
└─────────────┘      └─────────────────────────┘      └─────────────┘

流程:
1. Producer 初始化:向 TC 注册 transactional.id,恢复未完成事务
2. beginTransaction():客户端标记事务开始
3. send():消息暂存客户端,标记为事务消息
4. commitTransaction():
   a. TC 写入 __transaction_state 为 PREPARE
   b. 向各 Partition Leader 发送 Control Batch
   c. 收到全部 ACK 后,写入 __transaction_state 为 COMMITTED
   d. Partition 写入 COMMIT Marker,消息对消费者可见

幂等生产者基础

组件说明
PID(Producer ID)生产者启动时从 Broker 获取的全局唯一 ID
Sequence Number每个 Partition 维护单调递增序号
Broker 去重以 <PID, Partition, SeqNum> 为键拒绝重复写入

消费者隔离级别

  • read_uncommitted:可见所有消息(包括未提交和已中止)
  • read_committed(默认):只可见已提交事务的消息

5.3 RocketMQ vs Kafka 事务消息对比

维度RocketMQKafka
事务粒度消息 + 本地事务跨分区原子写入
回查机制Broker 主动回查 Producer无回查,依赖客户端状态
适用场景业务事务 + 消息发送流处理中的 exactly-once
消费可见性二次确认后可见COMMIT Marker 后可见

六、Seata — 一站式分布式事务框架

Seata 是目前国内最主流的分布式事务解决方案,整合了 AT、TCC、Saga、XA 四种模式。

6.1 AT 模式(Automatic Transaction)

核心创新:基于 SQL 解析自动生成回滚日志,对业务零侵入。

java
@Service
public class OrderService {
    
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // 1. 扣减库存(自动解析 SQL,生成 undo_log)
        inventoryService.deduct(order.getSkuId(), order.getQuantity());
        
        // 2. 创建订单
        orderDao.insert(order);
        
        // 3. 扣减余额
        accountService.debit(order.getUserId(), order.getAmount());
        
        // 任意步骤异常,自动全局回滚
    }
}

6.2 AT 模式内部执行流程

text
1. @GlobalTransactional 触发,向 TC 注册全局事务,生成 XID
2. 执行业务 SQL
3. RM(Resource Manager)拦截 SQL,解析生成前后镜像:
   - 前镜像:SELECT * FROM inventory WHERE sku_id = 1001  → 库存 100
   - 后镜像:UPDATE inventory SET stock = 90 WHERE sku_id = 1001
4. 将前后镜像写入 undo_log 表(同一本地事务)
5. 提交本地事务前,向 TC 申请全局锁(数据行的全局唯一锁)
6. 获取全局锁后,提交本地事务
7. 报告分支状态给 TC
8. 全局提交/回滚:
   - 成功 → 异步删除 undo_log
   - 失败 → 用前镜像数据生成回滚 SQL,执行补偿

6.3 全局锁机制

yaml
seata:
  client:
    lock:
      retry-interval: 10ms    # 锁重试间隔
      retry-times: 30         # 重试次数
      lock-expire-time: 60000 # 锁过期时间 60s(防死锁)

全局锁是 AT 模式保证一致性的核心:即使多个服务操作同一条数据,Seata 也能通过 TC 协调避免并发冲突。

6.4 四种模式速查

模式一致性性能侵入性适用场景
AT强一致零侵入微服务通用场景
TCC最终一致高并发、金融核心
Saga最终一致长流程、跨服务编排
XA强一致传统单体、短事务

七、分布式数据库的原生事务方案

7.1 TiDB — Percolator 模型

TiDB 采用 Google Percolator 论文实现的分布式事务,核心特点是全局 TSO(Timestamp Oracle)和 MVCC 多版本并发控制。

架构组件

组件职责
TiDB Server无状态 SQL 层,负责解析、执行计划生成
TiKV基于 Raft 的分布式 KV 存储,数据分片为 Region(默认 96MB)
PD(Placement Driver)全局元数据管理,分配 TSO(全局单调递增时间戳)

Percolator 两阶段提交流程

text
Prewrite 阶段(类似 2PC 的 Prepare):
  1. 选择主键(Primary Key),写入 Primary Lock
  2. 向其他键写入 Secondary Lock
  3. 同时写入数据的新版本(带 startTS 时间戳)
  
Commit 阶段:
  1. 检查 Primary Lock 是否存在(冲突检测)
  2. 获取 commitTS(全局唯一,来自 PD)
  3. 写入 Commit 记录(带 commitTS)
  4. 异步清理 Lock(Lazy 清理,减少阻塞)

乐观锁 vs 悲观锁

模式机制适用场景
乐观锁(默认)不加锁,提交时检查冲突,冲突则回滚重试读多写少、冲突低的场景
悲观锁类似传统数据库,先加锁再操作高冲突场景(如秒杀、库存扣减)

7.2 CockroachDB

类似 TiDB,基于 Raft 和 MVCC,但默认使用串行化隔离级别(Serializable),通过时间戳排序和重启事务解决冲突。适合对一致性要求极高、愿意接受一定事务重启代价的场景。

八、协调基础设施 — 分布式锁与共识

8.1 etcd — 基于 Raft 的分布式锁

etcd 基于 Raft 共识算法实现强一致性,其分布式锁利用以下特性:

特性作用
Revision 机制每个写操作生成全局唯一、单调递增的 Revision,确定锁获取顺序
Lease(租约)为 Key 设置 TTL,客户端崩溃后锁自动释放,避免死锁
Watch 机制监听前一个 Revision 的删除事件,实现公平锁(FIFO)
Txn 事务原子性 Compare-And-Swap,保证锁判断和创建的原子性

公平锁核心逻辑

go
// 核心思想:未获得锁的客户端监听前一个 Revision 的删除事件
// 而非监听锁本身,避免"羊群效应"(惊群问题)

func (m *Mutex) Lock(ctx context.Context) error {
    // 1. 创建自己的 Key(带 Lease,保证崩溃自动释放)
    m.myKey = fmt.Sprintf("%s%x", m.pfx, s.Lease())
    
    // 2. 原子性创建或获取
    cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0)
    put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease()))
    getOwner := v3.OpGet(m.pfx, v3.WithFirstCreate()...)
    
    resp, err := client.Txn(ctx).If(cmp).Then(put, getOwner).Else(get, getOwner).Commit()
    
    // 3. 判断自己是否是 Revision 最小的(最先创建的)
    m.myRev = resp.Header.Revision
    ownerKey := resp.Responses[1].GetResponseRange().Kvs[0]
    
    if ownerKey.CreateRevision == m.myRev {
        return nil // 获得锁
    }
    
    // 4. 未获得锁,监听前一个 Revision 的删除事件
    header := resp.Header
    waitKey := resp.Responses[1].GetResponseRange().Kvs[0].Key
    waitRev := resp.Responses[1].GetResponseRange().Kvs[0].ModRevision
    
    // 等待 waitKey 被删除
    <-client.Watch(ctx, string(waitKey), v3.WithRev(waitRev))
    
    return nil
}

8.2 ZooKeeper — ZAB 协议与分布式协调

ZooKeeper 基于 ZAB 协议(ZooKeeper Atomic Broadcast),优先保证消息全局顺序性。

作为 2PC 协调器的实现

  • 协调者在 ZK 上创建持久顺序节点 /transactions/txn-00000001
  • 参与者监听该节点状态变化
  • 协调者通过修改节点数据为 COMMITTEDROLLED_BACK 来驱动第二阶段

分布式锁实现

  • 创建临时顺序节点(EPHEMERAL_SEQUENTIAL
  • 判断自己是否是序号最小的节点,是则获得锁
  • 否则监听前一个节点的删除事件

九、选型决策指南

9.1 一致性要求与方案匹配

text
┌────────────────────────────────────────────────────────────────────┐
│  金融核心交易(强一致)                                            │
│  ├── 短事务、低并发 ──▶ MySQL XA / Seata XA 模式                   │
│  └── 高并发、高冲突 ──▶ Seata TCC 模式                             │
├────────────────────────────────────────────────────────────────────┤
│  电商业务(最终一致)                                              │
│  ├── 异步通知场景 ────▶ RocketMQ 事务消息 / Kafka 事务               │
│  ├── 长流程编排 ────▶ Seata Saga 模式                              │
│  └── 简单跨服务调用 ──▶ 本地消息表 + 任意 MQ                         │
├────────────────────────────────────────────────────────────────────┤
│  云原生/Serverless                                                 │
│  ├── 多语言混合 ──────▶ Dapr Actor / State Management                │
│  ├── 事件溯源架构 ────▶ Axon Framework / Eventuate                   │
│  └── 分布式数据库 ────▶ TiDB / CockroachDB(原生支持)               │
├────────────────────────────────────────────────────────────────────┤
│  协调与锁                                                          │
│  ├── 强一致锁 ────────▶ etcd / ZooKeeper                             │
│  ├── 高性能缓存锁 ────▶ Redis Redisson(注意主从一致性风险)          │
│  └── 分布式配置 ──────▶ etcd / Consul / Nacos                        │
└────────────────────────────────────────────────────────────────────┘

9.2 关键指标对比

方案一致性性能侵入性复杂度适用场景
MySQL XA强一致传统单体、短事务
Seata AT强一致零侵入微服务通用场景
Seata TCC最终一致高并发、金融核心
Seata Saga最终一致长流程、跨服务编排
RocketMQ最终一致异步通知、事件驱动
Kafka分区有序流处理、日志采集
TiDB可调零侵入海量数据、高可用
etcd强一致配置中心、服务发现、锁

9.3 选型核心原则

  1. 先确定一致性要求:强一致还是最终一致?这直接排除一半方案。
  2. 再评估性能需求:高并发场景下,2PC 往往成为瓶颈。
  3. 考虑业务侵入性:AT 模式零侵入,TCC 需要手写三阶段逻辑。
  4. 评估团队运维成本:引入 Seata Server、TiDB 集群都有额外的运维复杂度。

十、2025-2026 年技术趋势

10.1 云原生深度集成

  • Seata + Kubernetes:支持 Sidecar 模式部署,通过 Service Mesh(Istio)实现无侵入事务传播
  • Serverless 事务:AWS DynamoDB Transactions、Azure Cosmos DB 提供托管分布式事务,开发者无需关心底层协调

10.2 AI 驱动优化

  • 智能锁策略:基于机器学习预测冲突概率,动态选择乐观锁或悲观锁策略
  • 异常自愈:AI 识别网络分区、节点故障模式,自动选择最优恢复策略

10.3 多模数据库融合

  • TiDB + TiFlash:行存(TiKV)处理事务,列存(TiFlash)实时分析,一套系统支持 HTAP
  • Redis 持久化增强:Redis on Flash、RedisRaft 等项目尝试解决 Redis 事务和持久化短板

十一、总结

分布式事务没有银弹,选择方案时需权衡一致性要求、性能、复杂度、业务侵入性四个维度:

场景推荐方案
强一致性 + 短事务2PC/XA
强一致性 + 高并发TCC
最终一致 + 长流程Saga
最终一致 + 异步通知可靠消息(RocketMQ/Kafka)
云原生架构Dapr、TiDB 等原生支持
协调与锁etcd / ZooKeeper

理解各中间件的内部实现机制——MySQL 的 Redo/Binlog 协同、Kafka 的 TC 协调、RocketMQ 的半消息 Topic 替换、Seata AT 的 undo_log 与全局锁——是正确选型和故障排查的关键。

随着云原生和 AI 技术的发展,分布式事务正朝着更低侵入、更高自动化、更强可观测性的方向演进。

参考

Last updated: