Skip to content
第 14 章 架构 ⏱ 13 分钟阅读

第 14 章:分布式事务 ​

学习目标 ​

  • 理解分布式事务的 CAP/BASE 理论
  • 掌握 2PC、TCC、Saga、可靠消息四种方案
  • 学会根据业务场景选择方案
  • 避免分布式事务中的常见坑

一、CAP 定理 ​

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

分布式系统 P 必然存在,只能在 C 和 A 间取舍
java
// 经典场景:下单 + 扣库存
@Transactional
public void placeOrder(OrderDto dto) {
    // 1. 订单库 INSERT
    orderDao.insert(dto);
    // 2. 库存库 UPDATE
    inventoryDao.deduct(dto.skuId, dto.quantity);
}
// 网络抖动:订单成功,库存失败 = 数据不一致

二、BASE 理论 ​

基本可用 + 软状态 + 最终一致,放弃强一致,换可用性。

text
强一致:任何时候查,所有人看到的数据一致
最终一致:经过一段时间后,数据一致

适用场景:大多数业务(订单、库存、积分)

三、2PC(两阶段提交) ​

text
协调者(TC)
   ├─ prepare  ──→ 参与者 1
   ├─ prepare  ──→ 参与者 2
   ├─ prepare  ──→ 参与者 3
   │
   ├─ commit   ──→ 参与者 1
   ├─ commit   ──→ 参与者 2
   └─ commit   ──→ 参与者 3
java
// Atomikos / Narayana 实现
@Atomikos
public void placeOrder(OrderDto dto) {
    orderDao.insert(dto);
    inventoryDao.deduct(dto.skuId, dto.quantity);
}
text
优点:实现简单(声明式注解)
缺点:同步阻塞,协调者单点,数据不一致窗口

⚠️ 坑 1:2PC 协调者挂了,参与者永远等不到 commit,锁死资源。高并发场景慎用。

四、TCC(补偿事务) ​

三个阶段:Try → Confirm → Cancel

java
// Try:预留资源
public class OrderTccAction {
    @TwoPhaseBusinessAction(name = "orderTry")
    public boolean tryOrder(OrderDto dto) {
        // 订单状态 PENDING
        orderDao.insertPending(dto);
        return true;
    }

    @TwoPhaseBusinessAction(name = "orderConfirm")
    public boolean confirmOrder(OrderDto dto) {
        // 订单状态 CONFIRMED
        orderDao.updateStatus(dto.id, "CONFIRMED");
        return true;
    }

    @TwoPhaseBusinessAction(name = "orderCancel")
    public boolean cancelOrder(OrderDto dto) {
        // 订单状态 CANCELLED
        orderDao.delete(dto.id);
        return true;
    }
}
java
// 库存 TCC
public class InventoryTccAction {
    public boolean tryFreeze(OrderDto dto) {
        // 冻结库存:stock - frozenStock
        inventoryDao.freeze(dto.skuId, dto.quantity);
        return true;
    }

    public boolean confirmFreeze(OrderDto dto) {
        // 真实扣减
        inventoryDao.commitFreeze(dto.skuId, dto.quantity);
        return true;
    }

    public boolean cancelFreeze(OrderDto dto) {
        // 解冻
        inventoryDao.unfreeze(dto.skuId, dto.quantity);
        return true;
    }
}
text
优点:最终一致,无锁
缺点:业务侵入大,每个操作都要写三个方法

⚠️ 坑 2:TCC 的 cancel 写错逻辑 = 数据不一致。幂等 + 防悬挂 必须做好。

五、Saga 模式 ​

text
[订单] ──→ [支付] ──→ [库存] ──→ [物流]
   │          │          │          │
   ↓          ↓          ↓          ↓
[取消订单] [退款]   [恢复库存] [取消发货]
java
// Saga 编排
public class OrderSaga {
    public void placeOrder(OrderDto dto) {
        // 1. 创建订单
        orderService.create(dto);
        try {
            // 2. 支付
            paymentService.pay(dto);
        } catch (Exception e) {
            // 补偿:取消订单
            orderService.cancel(dto);
            throw e;
        }
        try {
            // 3. 扣库存
            inventoryService.deduct(dto);
        } catch (Exception e) {
            // 补偿:退款 + 取消订单
            paymentService.refund(dto);
            orderService.cancel(dto);
            throw e;
        }
    }
}
text
优点:实现简单,业务侵入小
缺点:补偿链长,中间状态难排查

六、可靠消息(本地消息表) ​

java
@Transactional
public void placeOrder(OrderDto dto) {
    // 1. 业务表 + 消息表,在同一事务
    orderDao.insert(dto);
    messageDao.insert(new OutboxMessage("stock.deduct", dto));

    // 2. 定时任务扫描消息表发送
}

// 定时任务
@Scheduled(fixedDelay = 1000)
public void sendPendingMessages() {
    List<OutboxMessage> messages = messageDao.findByStatus("PENDING");
    for (OutboxMessage msg : messages) {
        try {
            // 3. 发送到 MQ
            rocketMQ.send(msg.getTopic(), msg.getPayload());
            messageDao.updateStatus(msg.getId(), "SENT");
        } catch (Exception e) {
            // 重试
        }
    }
}
text
优点:可靠,不丢消息
缺点:有延迟(取决于扫描间隔)

七、各种方案对比 ​

方案一致性性能侵入适用
2PC强低小银行核心
TCC最终中大金融支付
Saga最终中中长事务
可靠消息最终高小异步解耦
最大努力弱极高极小通知类

八、避坑指南 ​

java
// 1. 幂等:任何操作都要可重试
// 业务表加唯一索引,或者用 Redis SETNX

// 2. 防悬挂:TCC cancel 比 try 先到
// 取消时判断"是否有对应 try 记录"

// 3. 空回滚:TCC try 没执行就 cancel
// 记录 try 状态,如果没 try 的 cancel 失败,要记录异常

// 4. 资源预留超时:TCC try 后 30 分钟不 confirm
// 定时任务扫描 Prepare 状态,超时 cancel

⚠️ 坑 3:为了"完全一致"上 2PC,导致订单接口 TPS 从 1000 跌到 100。互联网业务选最终一致,补偿 + 对账。

本章小结 ​

场景推荐方案
跨订单 + 库存Saga / 可靠消息
支付 + 退款TCC
异步通知可靠消息
简单业务单库事务

动手练习 ​

  1. TCC 实现:用 byteTCC 框架实现订单 + 库存两个操作
  2. Saga 编排:写一个 OrderSaga 长流程,任意一步失败触发补偿
  3. 本地消息表:实现订单 + 消息同事务,定时任务发送
  4. 幂等设计:任何一个接口加幂等校验,防止重复提交

下一章:第 15 章:Seata 实战 →

本站基于 VitePress 构建 · 由 StackHub 团队维护