找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4111

积分

0

好友

535

主题
发表于 昨天 20:44 | 查看: 6| 回复: 0

用户下单,涉及三个微服务:

订单服务:创建订单,状态改为“待支付”
支付服务:扣用户余额
库存服务:扣减商品库存

订单创建成功、支付扣款成功,但库存扣减失败——用户的钱已经扣了,库存没扣,这就超卖了

单机数据库一个事务就能搞定,但在分布式环境里,一个事务跨越三个服务、三个数据库,MySQL 的 BEGIN / COMMIT 管不了。

分布式事务就是来解决这种问题的。

白话版

两阶段提交(2PC)的婚礼比喻示意图

两种主流方案,用两个生活场景来类比:

2PC(两阶段提交) — 像婚礼上的交换戒指仪式:

第一阶段(准备阶段):
  司仪问:“新郎,你愿意娶新娘为妻吗?”
  新郎回答:“我愿意”
  司仪问:“新娘,你愿意嫁给新郎为妻吗?”
  新娘回答:“我愿意”
第二阶段(提交阶段):
  司仪宣布:“礼成!交换戒指!”
  (双方都说了愿意,仪式才正式完成)
  如果任何一方说“不愿意”,仪式取消

TCC(Try-Confirm-Cancel) — 像去餐厅订大餐:

Try(预订):提前打电话给餐厅,“我下周五要订一桌 10 人的包间,先帮我预留”
            (餐厅锁定了包间,但还没正式消费)
Confirm(确认):下周五你到店了,“确认用餐,上菜吧!”
                (正式扣款,真正消费)
Cancel(取消):如果你临时取消,“不好意思去不了了”
              (餐厅释放包间,不需要付钱)

方案一:2PC(两阶段提交)

2PC 是最经典的分布式事务方案,由一个协调者(Coordinator)和多个参与者(Participant)组成。

第一阶段:准备阶段(Prepare)

协调者(事务管理器)
    │
    ├──→ 订单服务:准备创建订单,写入 UNDO/REDO 日志
    │     ← 响应:准备就绪 ✅
    │
    ├──→ 支付服务:准备扣款,冻结余额,写入 UNDO/REDO 日志
    │     ← 响应:准备就绪 ✅
    │
    └──→ 库存服务:准备扣库存,写入 UNDO/REDO 日志
          ← 响应:准备就绪 ✅

如果任何一个参与者返回“准备失败”,协调者会通知所有参与者回滚

第二阶段:提交阶段(Commit)

协调者(事务管理器)
    │
    ├──→ 订单服务:提交 ✅
    ├──→ 支付服务:提交 ✅
    └──→ 库存服务:提交 ✅

协调者一旦宕机,参与者就会进入“不确定状态”——不知道应该提交还是回滚。这就是 2PC 最大的问题:阻塞

2PC 的缺陷

问题 说明 后果
同步阻塞 第一阶段所有参与者都必须等待协调者指令 资源被锁定,无法释放
单点故障 协调者挂了,整个事务卡住 参与者永远处于“待定”状态
数据不一致 第二阶段部分参与者提交成功,部分失败 协调者也挂了,没人知道该回滚还是提交
性能差 所有参与者都要同步等待,延迟高 不适合高并发场景

2PC 适用于传统单体架构的跨数据库事务,不适合微服务的高并发场景

方案二:TCC(Try-Confirm-Cancel)

TCC柔性事务的三阶段流程示意图

TCC 是分布式事务中最常用的“柔性事务”方案,它把每个服务的操作拆成三个阶段:

Try 阶段:资源预留

检查业务资源是否可用,并预留资源。

// 支付服务 - Try
public boolean tryPay(long userId, long amount) {
    // 1. 检查余额是否充足
    Account account = accountDao.getByUserId(userId);
    if (account.getBalance() < amount) {
        return false;  // 余额不足,Try 失败
    }
    // 2. 冻结资金(不扣款,只是预留)
    account.setFrozenAmount(account.getFrozenAmount() + amount);
    accountDao.update(account);
    // 3. 记录事务日志,用于后续 Confirm/Cancel 时的幂等
    transactionLogDao.save(new TransactionLog(userId, amount, "TRY"));
    return true;
}

Confirm 阶段:正式执行

确认执行,真正扣款。Confirm 必须保证幂等(多次执行结果一样)。

// 支付服务 - Confirm
public boolean confirmPay(long userId, long amount) {
    // 1. 幂等检查:是否已经 Confirm 过了?
    TransactionLog log = transactionLogDao.getByUserIdAndAmount(userId, amount);
    if (log.getStatus() == "CONFIRMED") {
        return true;  // 已经确认过了,直接返回成功
    }
    // 2. 真正扣款:从冻结资金中扣减
    Account account = accountDao.getByUserId(userId);
    account.setBalance(account.getBalance() - amount);
    account.setFrozenAmount(account.getFrozenAmount() - amount);
    accountDao.update(account);
    // 3. 更新事务日志
    log.setStatus("CONFIRMED");
    transactionLogDao.update(log);
    return true;
}

Cancel 阶段:取消回滚

取消操作,释放 Try 阶段预留的资源。Cancel 也必须保证幂等。

// 支付服务 - Cancel
public boolean cancelPay(long userId, long amount) {
    // 1. 幂等检查:是否已经 Cancel 过了?
    TransactionLog log = transactionLogDao.getByUserIdAndAmount(userId, amount);
    if (log.getStatus() == "CANCELLED") {
        return true;  // 已经取消过了,直接返回
    }
    // 2. 释放冻结资金
    Account account = accountDao.getByUserId(userId);
    account.setFrozenAmount(account.getFrozenAmount() - amount);
    accountDao.update(account);
    // 3. 更新事务日志
    log.setStatus("CANCELLED");
    transactionLogDao.update(log);
    return true;
}

TCC 与 2PC 的对比

2PC TCC
资源锁定 数据库层面锁行记录 业务层面预留资源(冻结/预占)
性能 差(同步阻塞,持有锁) 好(业务层预占,不阻塞数据库)
一致性 强一致(但可能阻塞) 最终一致
实现复杂度 低(数据库原生支持) 高(需要自己写 Try/Confirm/Cancel)
适用场景 跨数据库事务,并发量低 微服务架构,高并发
幂等要求 不要求 Confirm 和 Cancel 必须幂等
典型框架 Atomikos, Seata AT Seata TCC, ByteTCC

TCC 的常见问题

1. 空回滚

Try 还没调用,就收到了 Cancel。比如网络超时,协调者误以为 Try 失败,但实际 Try 根本未执行。

解法:在 Cancel 中判断是否有 Try 的记录,如果没有,直接返回成功(视为空回滚)。

2. 幂等控制

Confirm 和 Cancel 可能被调用多次,需要保证多次执行结果相同。

解法:使用事务日志表记录执行状态,每次执行前检查。

3. 悬挂

Cancel 先于 Try 执行(网络乱序),之后 Try 才到,导致预留的资源既不会被 Confirm 也不会被 Cancel。

解法:Try 中先检查是否已有 Cancel 记录,如果有,跳过预留操作。

什么时候用分布式事务,什么时候不用?

场景 推荐方案
跨数据库的强一致需求 2PC(Seata AT 模式)
微服务间的高并发事务 TCC(Seata TCC 模式)
非关键路径,允许短暂不一致 最终一致性(MQ 异步消息 + 补偿)
不能容忍任何失败 本地事务 + 本地消息表 + 定时补偿
最简单的方式 尽量避免分布式事务,通过业务设计规避

小结

分布式事务在微服务架构中绕不开。核心思路只有两种:

2PC(两阶段提交) — 像婚礼上的“我愿意”,所有人准备好了才正式执行。简单,但会阻塞,适合低并发场景。
TCC(Try-Confirm-Cancel) — 像订餐厅包间,先预留再确认。灵活但实现复杂,适合高并发微服务场景。

但说实话,最好的分布式事务策略就是:尽量避免分布式事务。通过合理的业务设计(比如把相关功能合并到同一个服务、用最终一致性代替强一致),很多时候你根本不需要分布式事务。如果你想深入了解分布式系统设计,欢迎访问云栈社区的后端 & 架构板块。




上一篇:DeepSeek-V4-Flash-0731 正式版发布 24 小时内被越狱:同行评审框架击穿 6/8 类安全护栏
下一篇:Gemini Spark 免费送云主机:Python 自动化脚本、无头浏览,跨境电商效率翻倍
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-2 07:39 , Processed in 0.749741 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表