强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 10 章:分布式事务

第 10 章:分布式事务

分布式事务是微服务架构中最棘手的问题之一。没有银弹,只有在一致性、可用性和复杂性之间做权衡。


10.1 为什么需要分布式事务

10.1.1 问题场景

  电商下单流程(跨多个服务):

  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ 订单服务  │    │ 库存服务  │    │ 支付服务  │    │ 积分服务  │
  │          │    │          │    │          │    │          │
  │ 创建订单  │───▶│ 扣减库存  │───▶│ 扣减余额  │───▶│ 增加积分  │
  └──────────┘    └──────────┘    └──────────┘    └──────────┘

  问题:如果"扣减余额"失败,前面已执行的操作怎么办?
  • 订单已创建 ✅ → 需要回滚
  • 库存已扣减 ✅ → 需要恢复
  • 余额扣减 ❌ → 失败点
  • 积分增加 → 未执行

  → 需要一种机制保证所有操作要么全部成功,要么全部回滚

10.1.2 分布式事务 vs 本地事务

维度本地事务 (ACID)分布式事务
原子性数据库保证需要额外机制
一致性强一致最终一致(多数方案)
隔离性数据库锁难以保证
持久性WAL 日志各服务独立保证
性能较低
复杂度

10.1.3 CAP 定理的约束

  CAP 定理:分布式系统最多同时满足其中两个

  C (Consistency)     一致性
  A (Availability)    可用性
  P (Partition Tolerance) 分区容错性

       C
      / \
     /   \
    /     \
   A ───── P

  网络分区必然存在 → 实际选择是 CP 或 AP

  CP:放弃可用性(如 ZooKeeper、Etcd)
  AP:放弃强一致性(如 Cassandra、DynamoDB)

  微服务通常选择 AP + 最终一致性

10.2 分布式事务解决方案总览

10.2.1 方案对比

方案一致性性能复杂度适用场景
2PC/XA强一致传统数据库事务
TCC最终一致资金操作
Saga最终一致长事务、跨服务流程
本地消息表最终一致异步场景
事务消息最终一致MQ 场景

10.3 两阶段提交(2PC / XA)

10.3.1 流程

  2PC (Two-Phase Commit) 流程:

  ┌──────────┐              ┌──────────┐
  │ 协调者    │              │ 参与者 A  │
  │ (Coordinator)│           │ (数据库A) │
  └─────┬────┘              └─────┬────┘
        │                         │
  ═══ 阶段1:准备 (Prepare) ════════════════════════════
        │                         │
        │  1. Prepare             │
        │────────────────────────▶│
        │                         │ 执行SQL,不提交
        │  2. Vote Yes/No         │
        │◀────────────────────────│
        │                         │
        │    ┌──────────┐         │
        │    │ 参与者 B  │         │
        │    │ (数据库B) │         │
        │    └─────┬────┘         │
        │          │              │
        │  1. Prepare             │
        │────────────────────────▶│
        │  2. Vote Yes/No         │
        │◀────────────────────────│
        │                         │
  ═══ 阶段2:提交/回滚 (Commit/Rollback) ════════════════
        │                         │
  所有都 Vote Yes?                │
        │                         │
  Yes:  │  3. Commit              │
        │────────────────────────▶│ 提交事务
        │                         │
  No:   │  3. Rollback            │
        │────────────────────────▶│ 回滚事务

10.3.2 2PC 的问题

问题说明
同步阻塞所有参与者在 Prepare 后锁定资源,等待 Commit
单点故障协调者崩溃导致所有参与者阻塞
数据不一致Commit 阶段部分参与者收不到消息
性能差全程锁定,吞吐量低

⚠️ 结论:2PC 不推荐在微服务中使用。传统单体应用的分布式数据库可以考虑 XA,但微服务架构应选择更现代的方案。


10.4 TCC 模式

10.4.1 概念

TCC(Try-Confirm-Cancel)是一种补偿型分布式事务模式,将每个操作分为三个阶段:

阶段说明示例(扣减库存)
Try预留资源(检查+冻结)冻结 10 件库存
Confirm确认提交(真正扣减)确认扣减冻结的 10 件
Cancel取消释放(回退预留)释放冻结的 10 件库存

10.4.2 TCC 流程

  TCC 执行流程:

  ┌────────┐        ┌────────┐        ┌────────┐
  │ 订单服务│        │ 库存服务│        │ 支付服务│
  │(发起者) │        │        │        │        │
  └───┬────┘        └───┬────┘        └───┬────┘
      │                 │                 │
  ═══ Try 阶段 ═══════════════════════════════
      │                 │                 │
      │  Try 冻结库存    │                 │
      │────────────────▶│                 │
      │  Try 成功 ✅    │                 │
      │◀────────────────│                 │
      │                 │                 │
      │  Try 冻结余额    │                 │
      │──────────────────────────────────▶│
      │  Try 成功 ✅    │                 │
      │◀──────────────────────────────────│
      │                 │                 │
  ═══ 全部 Try 成功 → Confirm 阶段 ════════════
      │                 │                 │
      │  Confirm 扣减    │                 │
      │────────────────▶│                 │
      │  Confirm 成功 ✅│                 │
      │                 │                 │
      │  Confirm 扣款    │                 │
      │──────────────────────────────────▶│
      │  Confirm 成功 ✅│                 │
      │                 │                 │

  ═══ 如果有 Try 失败 → Cancel 阶段 ════════════
      │                 │                 │
      │  Cancel 释放冻结  │                 │
      │────────────────▶│                 │
      │  Cancel 成功 ✅ │                 │

10.4.3 TCC 实现要点

// TCC 接口定义
public interface InventoryTccService {
    // Try: 冻结库存
    @TwoPhaseBusinessAction(name = "deductInventory",
        commitMethod = "confirm",
        rollbackMethod = "cancel")
    boolean tryDeduct(
        @BusinessActionContextParameter(paramName = "orderId") String orderId,
        @BusinessActionContextParameter(paramName = "productId") String productId,
        @BusinessActionContextParameter(paramName = "quantity") int quantity
    );

    // Confirm: 确认扣减
    boolean confirm(BusinessActionContext context);

    // Cancel: 释放冻结
    boolean cancel(BusinessActionContext context);
}

// 实现
@Service
public class InventoryTccServiceImpl implements InventoryTccService {

    @Override
    @Transactional
    public boolean tryDeduct(String orderId, String productId, int quantity) {
        // 1. 检查可用库存是否足够
        int available = inventoryMapper.getAvailable(productId);
        if (available < quantity) return false;

        // 2. 冻结库存(available -= quantity, frozen += quantity)
        inventoryMapper.freeze(productId, orderId, quantity);
        return true;
    }

    @Override
    @Transactional
    public boolean confirm(BusinessActionContext context) {
        String orderId = (String) context.getActionContext("orderId");
        String productId = (String) context.getActionContext("productId");
        // 确认扣减(frozen -= quantity, total -= quantity)
        inventoryMapper.confirmDeduct(productId, orderId);
        return true;
    }

    @Override
    @Transactional
    public boolean cancel(BusinessActionContext context) {
        String orderId = (String) context.getActionContext("orderId");
        String productId = (String) context.getActionContext("productId");
        // 释放冻结(available += quantity, frozen -= quantity)
        inventoryMapper.releaseFrozen(productId, orderId);
        return true;
    }
}

10.4.4 TCC 的空回滚和悬挂问题

问题说明解决方案
空回滚Try 没执行,Cancel 先到Cancel 时检查 Try 是否执行过
悬挂Cancel 先执行,Try 后到Try 时检查 Cancel 是否已执行
幂等Confirm/Cancel 重复调用使用事务 ID 去重
  空回滚:
  Try 请求丢失 ──▶ 协调者超时 ──▶ 调用 Cancel
  Cancel 发现没有冻结记录 → 记录"已回滚"标记

  悬挂:
  Cancel 先执行 ──▶ Try 后到达 ──▶ Try 发现"已回滚"标记 → 不执行

10.5 Saga 模式

10.5.1 概念

Saga 将长事务拆分为一系列本地事务,每个本地事务有对应的补偿操作。如果某一步失败,则反向执行之前所有步骤的补偿操作。

  Saga 两种实现方式:

  1. 编排式 (Choreography) — 事件驱动
  2. 编排式 (Orchestration) — 中央协调

10.5.2 编排式 Saga(Choreography)

  事件驱动的 Saga:

  ┌────────┐   OrderCreated   ┌────────┐  PaymentCompleted  ┌────────┐
  │ 订单服务│ ──────────────▶ │ 支付服务│ ◀───────────────── │        │
  │        │                  │        │                     │        │
  │ 创建订单│   InventoryFrozen│ 扣减余额│   OrderConfirmed   │        │
  │        │ ◀────────────── │        │ ─────────────────▶ │        │
  └────┬───┘                  └────────┘                     └────────┘
       │                         │
       │ OrderCancelled          │ PaymentFailed
       ▼                         ▼
  ┌────────┐                ┌────────┐
  │ 库存服务│                │ 订单服务│
  │ 恢复库存│                │ 取消订单│
  └────────┘                └────────┘

  正向流程:Order → Payment → Inventory → Confirm
  补偿流程:Inventory 恢复 → Payment 退款 → Order 取消

10.5.3 编排式 Saga(Orchestration)

  中央协调的 Saga:

  ┌──────────────────────────────────────────────────────┐
  │              Saga 协调器 (Orchestrator)                │
  │                                                      │
  │  ┌────────────────────────────────────────────────┐  │
  │  │  Saga 定义:                                    │  │
  │  │  Step 1: 创建订单 → Compensate: 取消订单        │  │
  │  │  Step 2: 冻结库存 → Compensate: 恢复库存        │  │
  │  │  Step 3: 扣减余额 → Compensate: 退还余额        │  │
  │  │  Step 4: 增加积分 → Compensate: 扣除积分        │  │
  │  └────────────────────────────────────────────────┘  │
  └──────────────────────────┬───────────────────────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
  ┌──────────┐        ┌──────────┐        ┌──────────┐
  │ 订单服务  │        │ 库存服务  │        │ 支付服务  │
  └──────────┘        └──────────┘        └──────────┘

  协调器根据每一步的执行结果决定:
  → 成功:执行下一步
  → 失败:反向执行补偿操作

10.5.4 Saga 状态机

  ┌──────────┐
  │  START   │
  └────┬─────┘
       ▼
  ┌──────────┐   成功    ┌──────────┐   成功    ┌──────────┐
  │ Step 1   │ ───────▶ │ Step 2   │ ───────▶ │ Step 3   │
  │ 创建订单  │          │ 冻结库存  │          │ 扣减余额  │
  └────┬─────┘          └────┬─────┘          └────┬─────┘
       │失败                 │失败                 │失败
       ▼                    ▼                    ▼
  ┌──────────┐        ┌──────────┐        ┌──────────┐
  │COMPENSATE│        │COMPENSATE│        │COMPENSATE│
  │ (无需补偿)│        │ 取消订单  │        │ 恢复库存  │
  └────┬─────┘        │ 取消订单  │        │ 取消订单  │
       │              └──────────┘        └──────────┘
       ▼
  ┌──────────┐
  │ ABORTED  │
  └──────────┘

  如果 Step 3 失败:
  执行补偿 Step 2 (恢复库存) → 执行补偿 Step 1 (取消订单) → ABORTED

10.5.5 Saga vs TCC

维度SagaTCC
资源锁定不锁定(正向操作)Try 阶段锁定
一致性最终一致最终一致
补偿难度需要补偿操作Cancel 就是补偿
性能更好(无锁定)略差(Try 锁定)
实现复杂度中等较高(处理空回滚/悬挂)
适用场景长流程业务资金类操作

10.6 本地消息表

10.6.1 模式

  本地消息表流程:

  ┌──────────────────────────────────────┐
  │  数据库事务                            │
  │  ┌──────────────────────────────────┐│
  │  │  INSERT INTO orders ...          ││
  │  │  INSERT INTO local_messages      ││  ← 同一个事务
  │  │    (type, payload, status=PENDING)││
  │  └──────────────────────────────────┘│
  └──────────────────────────────────────┘
                     │
                     ▼
  ┌──────────────────────────────────────┐
  │  后台任务:扫描 local_messages 表     │
  │  发送 status=PENDING 的消息到 MQ      │
  │  更新 status=SENT                    │
  └───────────────────┬──────────────────┘
                      │
                      ▼
  ┌──────────────────────────────────────┐
  │  消费者消费消息,处理业务逻辑           │
  │  处理成功后回调确认                    │
  └──────────────────────────────────────┘

10.6.2 与事务性发件箱的区别

两者本质相同,都是保证"业务操作"和"消息发送"的原子性:

维度本地消息表事务性发件箱 (Outbox)
消息提取定时任务轮询CDC (Debezium) 监听 Binlog
实时性秒级(取决于轮询间隔)毫秒级
数据库压力有(频繁查询)低(只读 Binlog)
实现难度

10.7 方案选型指南

  分布式事务方案选型决策树:

  需要强一致性吗?
  │
  ├── 是 → 数据量小 + 性能要求不高?
  │        ├── 是 → 考虑 2PC/XA(传统方案)
  │        └── 否 → 考虑 TCC(冻结资源)
  │
  └── 否(最终一致性可接受)→
           │
           ├── 有消息队列吗?
           │   ├── 是 → 事务消息 / 本地消息表
           │   └── 否 → Saga 模式
           │
           └── 流程长 + 参与者多?
               ├── 是 → Saga(编排式/协调式)
               └── 否 → 本地消息表

10.8 业务场景:电商下单的分布式事务

  采用 Saga 模式:

  ┌─────────────────────────────────────────────────────────────┐
  │  Saga 协调器: OrderSaga                                     │
  │                                                             │
  │  Step 1: 创建订单 (order-service)                           │
  │    → 补偿: 取消订单                                         │
  │                                                             │
  │  Step 2: 扣减库存 (inventory-service)                       │
  │    → 补偿: 恢复库存                                         │
  │                                                             │
  │  Step 3: 扣减余额 (payment-service)                         │
  │    → 补偿: 退还余额                                         │
  │                                                             │
  │  Step 4: 增加积分 (points-service)                          │
  │    → 补偿: 扣除积分                                         │
  │                                                             │
  │  Step 5: 发送通知 (notification-service)                    │
  │    → 补偿: 无需补偿(通知已发送不影响业务)                    │
  │                                                             │
  │  异常处理:                                                  │
  │  • Step 3 失败 → 执行 Step 2 补偿 + Step 1 补偿 → ABORTED  │
  │  • Step 1 超时 → ABORTED(直接中止)                        │
  └─────────────────────────────────────────────────────────────┘

⚠️ 注意事项

  1. 优先考虑是否真的需要分布式事务——很多场景可以通过业务设计避免
  2. 补偿操作必须幂等——因为补偿可能被重复执行
  3. 补偿不一定完美——有些操作(如发送短信)无法真正回滚
  4. Saga 的隔离性差——中间状态对外可见,需要额外处理
  5. TCC 的 Try 要轻量——Try 阶段不能做太重的操作

📖 扩展阅读

  1. Chris Richardson - Microservices Patterns Chapter 4 — Saga 模式详解
  2. Seata (seata.io) — 阿里开源的分布式事务框架
  3. Temporal.io — Saga 编排引擎
  4. Eventuate Tram — Chris Richardson 的事务性发件箱实现
  5. Distributed Sagas: A Protocol for Coordinating Microservices — Caitie McCaffrey

本章小结

方案一致性性能复杂度推荐场景
2PC/XA传统数据库(不推荐微服务)
TCC最终资金/金融操作
Saga最终长流程业务
本地消息表最终异步解耦

📌 下一章第 11 章:可观测性 — 链路追踪、日志聚合、指标监控的完整方案。