分布式事务完全演进链:从单体事务到 TCC 、Saga 与最终一致性

分布式事务完全演进链:从单体事务到 TCC 、Saga 与最终一致性

  • 第一部分:单体事务的底层机制彻底理解
    • [1. 什么是事务(Transaction)](#1. 什么是事务(Transaction))
      • [1.1 一句话定义](#1.1 一句话定义)
      • [1.2 事务真正依赖的不是"运气",而是 ACID](#1.2 事务真正依赖的不是“运气”,而是 ACID)
      • [1.3 一个贯穿全文的例子:银行转账](#1.3 一个贯穿全文的例子:银行转账)
    • [2. 单体事务的底层机制(数据库到底做了什么)](#2. 单体事务的底层机制(数据库到底做了什么))
      • [2.1 日志机制:Undo log / Redo log](#2.1 日志机制:Undo log / Redo log)
      • [2.2 锁机制:事务隔离的真正来源](#2.2 锁机制:事务隔离的真正来源)
      • [2.3 隔离级别:数据库如何在"正确"和"性能"之间取舍](#2.3 隔离级别:数据库如何在“正确”和“性能”之间取舍)
      • [2.4 缓冲区与提交机制:为什么事务不慢](#2.4 缓冲区与提交机制:为什么事务不慢)
      • [2.5 回滚机制:事务失败时,系统如何自救](#2.5 回滚机制:事务失败时,系统如何自救)
    • [3. 为什么单体事务无法跨库(这是分布式事务的起点)](#3. 为什么单体事务无法跨库(这是分布式事务的起点))
    • [4. 本部分你必须牢牢记住的几件事](#4. 本部分你必须牢牢记住的几件事)
  • [第二部分:分布式事务的本质问题与 2PC 协议](#第二部分:分布式事务的本质问题与 2PC 协议)
    • [1. 分布式事务到底难在哪里](#1. 分布式事务到底难在哪里)
      • [1.1 单体事务 vs 分布式事务:难度差异不是一个量级](#1.1 单体事务 vs 分布式事务:难度差异不是一个量级)
      • [1.2 一个典型失败场景](#1.2 一个典型失败场景)
      • [1.3 分布式事务失败的四个根因(非常重要)](#1.3 分布式事务失败的四个根因(非常重要))
    • [2. 2PC:第一个系统级解决方案](#2. 2PC:第一个系统级解决方案)
      • [2.1 2PC 解决的不是"提交",而是"一致决策"](#2.1 2PC 解决的不是“提交”,而是“一致决策”)
      • [2.2 角色模型(必须记住)](#2.2 角色模型(必须记住))
    • [3. 两阶段提交的完整流程(拆到原子级)](#3. 两阶段提交的完整流程(拆到原子级))
    • [4. 2PC 的致命问题](#4. 2PC 的致命问题)
      • [4.1 为什么 2PC 是"阻塞型协议"](#4.1 为什么 2PC 是“阻塞型协议”)
      • [4.2 CAP 角度再看 2PC](#4.2 CAP 角度再看 2PC)
    • [5. 2PC 的优缺点](#5. 2PC 的优缺点)
    • [6. 为什么 2PC 没有被广泛使用?](#6. 为什么 2PC 没有被广泛使用?)
      • [6.1 2PC 的三个"理想前提"](#6.1 2PC 的三个“理想前提”)
      • [6.2 2PC 的"致命工程缺陷"](#6.2 2PC 的“致命工程缺陷”)
      • [6.3 工业界的选择:放弃"强一致",拥抱"可用性"](#6.3 工业界的选择:放弃“强一致”,拥抱“可用性”)
      • [6.4 一句话总结](#6.4 一句话总结)
    • 总结
  • [第三部分:TCC ------ 把事务控制权从数据库交还给业务](#第三部分:TCC —— 把事务控制权从数据库交还给业务)
    • [1. TCC 出现之前的核心问题](#1. TCC 出现之前的核心问题)
    • [2. TCC 核心思想:事务不是一瞬间完成的](#2. TCC 核心思想:事务不是一瞬间完成的)
    • [3. 完整库存场景理解 TCC](#3. 完整库存场景理解 TCC)
      • [3.1 错误直觉](#3.1 错误直觉)
      • [3.2 正确 TCC 库存模型](#3.2 正确 TCC 库存模型)
    • [4. Try 阶段到底改没改数据库?](#4. Try 阶段到底改没改数据库?)
    • [5. 读旧数据问题解析](#5. 读旧数据问题解析)
      • [5.1 事务参与者](#5.1 事务参与者)
      • [5.2 用户查询接口](#5.2 用户查询接口)
    • [6. TCC 的"锁"在哪里?](#6. TCC 的“锁”在哪里?)
    • [7. Confirm 与 Cancel:事务真正结局](#7. Confirm 与 Cancel:事务真正结局)
      • [Confirm 阶段](#Confirm 阶段)
      • [Cancel 阶段](#Cancel 阶段)
    • [8. TCC 的三个隐藏难点](#8. TCC 的三个隐藏难点)
    • [9. Try 成功但 Confirm 不来怎么办?](#9. Try 成功但 Confirm 不来怎么办?)
    • [10. TCC 的适用场景与限制](#10. TCC 的适用场景与限制)
    • [11. 核心记忆总结](#11. 核心记忆总结)
  • [第四部分:Saga ------ 接受不一致,用时间换系统存活](#第四部分:Saga —— 接受不一致,用时间换系统存活)
    • [1. Saga 出现的根本原因:长事务 TCC 承受不了](#1. Saga 出现的根本原因:长事务 TCC 承受不了)
    • [2. Saga 的核心思想:事务不是"一次性提交"](#2. Saga 的核心思想:事务不是“一次性提交”)
    • [3. Saga 的基本模型](#3. Saga 的基本模型)
    • [4. Saga 与 TCC 的本质对比](#4. Saga 与 TCC 的本质对比)
    • [5. Saga 的两种实现方式](#5. Saga 的两种实现方式)
      • [编排式 Saga(Orchestration)](#编排式 Saga(Orchestration))
      • [事件驱动 Saga(Choreography)](#事件驱动 Saga(Choreography))
    • [6. Saga 的关键工程难点](#6. Saga 的关键工程难点)
    • [7. Saga 的适用场景](#7. Saga 的适用场景)
    • [8. 工程实践技巧](#8. 工程实践技巧)
    • [9. 总结与记忆点](#9. 总结与记忆点)
  • [第五部分:可靠消息 / 事务消息 ------ 把一致性交给时间](#第五部分:可靠消息 / 事务消息 —— 把一致性交给时间)
    • [1. 先抛结论:为什么消息几乎是最终答案?](#1. 先抛结论:为什么消息几乎是最终答案?)
    • [2. 一个最真实的业务问题(几乎人人都踩过)](#2. 一个最真实的业务问题(几乎人人都踩过))
    • [3. 为什么"数据库事务 + 发消息"一定会失败?](#3. 为什么“数据库事务 + 发消息”一定会失败?)
    • [4. 可靠消息的核心模式:本地事务 + 消息表(关键转折)](#4. 可靠消息的核心模式:本地事务 + 消息表(关键转折))
      • [1️⃣ 核心思想(一句话)](#1️⃣ 核心思想(一句话))
      • [2️⃣ 表结构示意(极简但够用)](#2️⃣ 表结构示意(极简但够用))
      • [3️⃣ 正确的执行流程(必须理解透)](#3️⃣ 正确的执行流程(必须理解透))
        • [Step 1:本地事务(原子保证)](#Step 1:本地事务(原子保证))
        • [Step 2:异步投递(解耦关键)](#Step 2:异步投递(解耦关键))
      • 本节记忆总结
    • [5. 这套模型为什么"天然可靠"?](#5. 这套模型为什么“天然可靠”?)
      • [场景 1:事务提交失败](#场景 1:事务提交失败)
      • [场景 2:事务成功,但服务宕机](#场景 2:事务成功,但服务宕机)
      • [场景 3:消息发出,但 ACK 丢失](#场景 3:消息发出,但 ACK 丢失)
      • 本节记忆总结
    • [6. 那"消息重复"怎么办?(必须吃透)](#6. 那“消息重复”怎么办?(必须吃透))
    • [7. 事务消息(以 RabbitMQ 思想为例)](#7. 事务消息(以 RabbitMQ 思想为例))
    • [8. 可靠消息 vs TCC vs Saga(最终对照)](#8. 可靠消息 vs TCC vs Saga(最终对照))
    • [9. 可靠消息的隐性成本](#9. 可靠消息的隐性成本)
      • [1️⃣ 延迟](#1️⃣ 延迟)
      • [2️⃣ 状态复杂度](#2️⃣ 状态复杂度)
      • [3️⃣ 运维成本](#3️⃣ 运维成本)
      • 本节记忆总结
  • 第六部分:如何在真实系统中选择分布式事务方案
    • [1. 先给你一个残酷但真实的结论](#1. 先给你一个残酷但真实的结论)
    • [2. 为什么"单体事务"能解决的问题,千万别拆?](#2. 为什么“单体事务”能解决的问题,千万别拆?)
    • [3. 什么时候你"被迫"进入分布式事务?](#3. 什么时候你“被迫”进入分布式事务?)
    • [4. 一套真实可用的"选择决策树"**](#4. 一套真实可用的“选择决策树”**)
    • [5. 一个完整的现实系统是怎么混用的?**](#5. 一个完整的现实系统是怎么混用的?**)
    • [6. 最常见、最致命的 5 个误区**](#6. 最常见、最致命的 5 个误区**)
      • [❌ 误区 1:追求"全局强一致"](#❌ 误区 1:追求“全局强一致”)
      • [❌ 误区 2:到处用 TCC](#❌ 误区 2:到处用 TCC)
      • [❌ 误区 3:把 MQ 当数据库](#❌ 误区 3:把 MQ 当数据库)
      • [❌ 误区 4:忽略幂等和重试](#❌ 误区 4:忽略幂等和重试)
      • [❌ 误区 5:设计时不考虑失败](#❌ 误区 5:设计时不考虑失败)
    • [7. 你必须形成的三种"工程直觉"](#7. 你必须形成的三种“工程直觉”)
    • [8. 如果让你"从零设计一个系统",该怎么做?](#8. 如果让你“从零设计一个系统”,该怎么做?)
    • [9. 你现在应该真正"吃透"的分布式事务观](#9. 你现在应该真正“吃透”的分布式事务观)

分布式事务,是现代微服务架构中最核心、最复杂、也最容易被误解的问题之一。

它难,不是因为概念多,而是因为:

  • 每一种方案,都不是"完美解"

  • 每一次演进,都是在为上一代方案还债

  • 每一个结论,背后都是工程妥协的结果

要真正理解分布式事务,必须从单体事务开始,一路看清:

为什么单体事务可以"理所当然地正确" 为什么一拆服务,正确性立刻崩塌 为什么方案会从 2PC → TCC → Saga →

消息一致性一路演进

本文的目标不是"记住方案",而是:

👉 理解:每一种方案为什么会出现,又为什么不可能成为终点


第一部分:单体事务的底层机制彻底理解

在理解任何分布式事务方案之前,有一件事是绝对绕不过去的

分布式事务之所以复杂,不是因为"分布式",而是因为:单体事务本身就已经很复杂。

如果你没有真正吃透单体事务的底层机制,那么后面所有方案------

2PC、TCC、Saga、消息一致性------都会变成"记结论,而不是理解原理"。

这一部分的目标只有一个:

👉 把数据库已经默默替你做了十几年的事,看清楚、想明白。


1. 什么是事务(Transaction)

1.1 一句话定义

事务,是一组操作的集合:

要么全部成功,要么全部失败,
不允许"做了一半"的中间状态被外界看到。

这是一个非常"反直觉"的能力,因为在现实世界中:

  • 程序会崩溃

  • 机器会断电

  • 网络会中断

但数据库却承诺:即使发生上述任意情况,也不会留下"半成品"。


1.2 事务真正依赖的不是"运气",而是 ACID

事务之所以可信,靠的不是"代码写得好",而是一整套底层机制设计,这就是 ACID。

特性 表面含义 真正解决的问题 底层依赖机制
原子性(Atomicity) 要么全做,要么全不做 防止"做了一半" Undo log
一致性(Consistency) 数据始终合法 防止脏数据 约束 / 外键 / 业务
隔离性(Isolation) 互不干扰 防止并发错乱 锁 + MVCC
持久性(Durability) 提交后不丢 防止宕机丢数据 Redo log + 刷盘

⚠️ 非常重要的一点

ACID 不是四个"口号",

而是四类非常具体、非常工程化的实现手段


1.3 一个贯穿全文的例子:银行转账

A 给 B 转账 100 元

拆开来看只有两步:

  1. A 账户余额 -100

  2. B 账户余额 +100

但真正的问题是:

❓如果第 1 步成功,第 2 步失败,怎么办?

事务存在的意义,就是让系统回答:

👉 "我可以保证:世界上永远不存在这种状态。"


2. 单体事务的底层机制(数据库到底做了什么)

这一节非常关键,因为你后面会反复看到:分布式事务方案,本质上都是在"模拟"或"替代"下面这些机制


2.1 日志机制:Undo log / Redo log

先给结论(一定要记住)

🔥 Undo log 管"后悔",Redo log 管"不丢"。


  • Undo log:为"失败"而生

    Undo log 记录的是:

    "修改之前的数据是什么"

    它解决的是一个核心问题:

    ❓如果事务执行到一半失败了,

    我怎么把数据库恢复到"什么都没发生过"的状态?

    工作方式非常直接:

    • 每次修改数据前
    • 先把"旧值"记录下来
    • 一旦失败,就按日志反向恢复

  • Redo log:为"成功"而生

    Redo log 记录的是:

    "修改之后的数据是什么"

    它解决的是另一个完全不同的问题:

    ❓事务已经告诉用户"成功了",

    但机器突然断电了,怎么办?

    数据库的答案是:

    • 先写日志

    • 再认为事务成功

    • 即使数据页还在内存里,也能靠日志重放


  • 把两者放在一起看(关键理解)

    text 复制代码
    事务开始
      A 账户 1000 → 900   (记录 Undo:1000)
      B 账户 1000 → 1100  (记录 Undo:1000)
    事务提交
      Redo log 刷盘
    • ❌ 失败 → 用 Undo log 回滚

    • ❌ 宕机 → 用 Redo log 重做

    👉 正是这"两套日志",撑起了事务的原子性和持久性。


2.2 锁机制:事务隔离的真正来源

如果说日志解决的是"成与败",

那锁解决的就是另一个问题:

多个人同时操作同一份数据,会不会乱?


锁的本质是什么?

锁不是为了"慢别人",而是为了:

让并发世界,看起来像串行世界。


常见锁类型
锁类型 作用 特点
行锁 锁定一行数据 并发高,成本高
表锁 锁定整张表 简单,但并发低

如果没有锁,会发生什么?

两个人同时给同一个账户转账

  • 都读到余额 100

  • 都扣 80

  • 最终余额变成 -60

👉 这不是"逻辑 bug",这是并发失控。


2.3 隔离级别:数据库如何在"正确"和"性能"之间取舍

数据库并不是非黑即白,而是给你提供了 4 种"妥协方案"

隔离级别 能看到什么 会出现的问题
读未提交 别人没提交的数据 脏读
读已提交 已提交的数据 不可重复读
可重复读 多次读一致 幻读(部分实现避免)
序列化 完全隔离 性能最差

👉 隔离级别的本质是:

你愿意为"更强一致性",付出多少性能代价。


2.4 缓冲区与提交机制:为什么事务不慢

如果每次事务都:

  • 直接写磁盘

  • 同步刷盘

那数据库早就不可用了。

所以真实流程是:

  1. 数据先写入内存缓冲

  2. Redo log 顺序写磁盘

  3. 提交时对外"可见"

常见优化手段
  • 批量刷盘(减少 IO)

  • 异步刷盘(性能更高,风险更大)

👉 这也是为什么"持久性"在现实中是"可配置的"。


2.5 回滚机制:事务失败时,系统如何自救

事务失败时,数据库做的事情非常机械,但极其可靠:

  1. 查 Undo log

  2. 反向恢复数据

  3. 释放锁

⚠️ 一个常被忽略的事实

即使系统崩溃,
下次启动时,数据库也会继续"未完成的回滚"。


3. 为什么单体事务无法跨库(这是分布式事务的起点)

到这里,你已经清楚:

单体事务之所以可靠,是因为:

  • 日志
  • 提交顺序
    全部在一个数据库内部完成

而一旦跨库,问题立刻出现。


跨库的三大致命问题

问题 表象 根本原因
原子性失效 一个库成功,一个失败 日志无法统一
网络不可靠 调用中断 节点随时可能失联
无全局协调 提交顺序不可控 没有统一事务管理者

一个非常形象的比喻

三个人搬苹果,每人负责一箱。

  • 前两个人已经放好

  • 第三个人半路摔倒

👉 结果不是"失败",而是"一半成功"。

而数据库事务存在的意义,恰恰是:

禁止这种世界状态出现。


4. 本部分你必须牢牢记住的几件事

核心认知锚点

  1. 事务不是魔法,是日志 + 锁 + 协调

  2. ACID 的每一项,背后都有具体工程实现

  3. 单体事务的强一致性,来自"单一控制边界"

  4. 一旦跨库,所有保障机制立刻失效


高频易混理解澄清

  • Undo log ≠ Redo log

  • 回滚 ≠ 删除数据

  • 隔离级别 ≠ 正确与否,而是取舍

  • 单体事务做不到的事,不是"设计不好",而是"边界决定"


自然引出下一问

❓既然单体事务做不到跨库,

那有没有一种方式,能把"事务"扩展到多个系统?

👉 这正是分布式事务诞生的原因。


第二部分:分布式事务的本质问题与 2PC 协议

一句话先立结论

分布式事务不是"数据库的问题",而是 多个自治系统之间如何达成一致的问题

而 2PC,是人类第一次用"协议"正面硬刚这个问题。


1. 分布式事务到底难在哪里

1.1 单体事务 vs 分布式事务:难度差异不是一个量级

在单体事务中:

  • 日志、锁、缓冲区

  • 都在同一个进程 / 同一个数据库

  • 失败可控,恢复路径确定

但在分布式系统中:

❌ 没有"同一个地方"

❌ 没有"统一时钟"

❌ 没有"绝对可靠的网络"


1.2 一个典型失败场景

电商下单流程:

  1. 库存服务:扣减库存(成功)

  2. 订单服务:创建订单(未执行)

  3. 支付服务:扣款(失败)

最终状态:

服务 状态
库存 ❌ 已扣
订单 ❌ 不存在
支付 ❌ 未扣

👉 这不是 Bug,而是分布式系统的"常态风险"


1.3 分布式事务失败的四个根因(非常重要)

根因 本质解释
原子性无法天然保证 每个服务只保证"本地原子性"
网络不可靠 超时 ≠ 失败,失败 ≠ 不可达
节点随时宕机 服务、数据库、协调者都可能消失
没有天然的全局锁 锁、日志、MVCC 都是本地能力

💡 核心结论

分布式事务不是"怎么提交",而是 怎么在不可信环境中达成一致决策


2. 2PC:第一个系统级解决方案

2.1 2PC 解决的不是"提交",而是"一致决策"

2PC(Two-Phase Commit)解决的是一个问题:

在多个参与者之间,如何保证:

  • 要么大家一起提交
  • 要么大家一起回滚

2.2 角色模型(必须记住)

角色 职责
协调者(Coordinator) 全局事务决策者
参与者(Participant) 本地事务执行者
事务日志 唯一可信的"记忆"

⚠️ 注意:
协调者不是数据库的一部分,而是"协议角色"


3. 两阶段提交的完整流程(拆到原子级)


阶段一:Prepare(投票阶段)

目标只有一个:确认"有没有人会失败"

执行步骤:

  1. 协调者发送 Prepare 请求

  2. 参与者:

    • 开启本地事务

    • 执行业务逻辑

    • 写 Undo / Redo log

    • 加锁,但不提交

  3. 返回:

    • YES(我能提交)

    • NO(我必须回滚)

本质理解:

Prepare 阶段 = "锁资源 + 写日志 + 表态"


阶段二:Commit / Rollback(决策阶段)

分支一:全部 YES → Commit

  1. 协调者发送 Commit

  2. 参与者:

    • 提交本地事务

    • 释放锁

    • 清理 Undo log

分支二:任何 NO → Rollback

  1. 协调者发送 Rollback

  2. 参与者:

    • 根据 Undo log 回滚

    • 释放锁


关键流程图

复制代码
Coordinator
    |
    |--- Prepare ---> Participant A
    |--- Prepare ---> Participant B
    |<--- YES -------|
    |<--- YES -------|
    |
    |--- Commit ----> Participant A
    |--- Commit ----> Participant B

4. 2PC 的致命问题

4.1 为什么 2PC 是"阻塞型协议"

因为 Prepare 成功之后,参与者不能自己决定回滚

致命场景:

  1. 所有参与者返回 YES

  2. 协调者宕机

  3. 参与者状态:

状态
本地事务 未提交
❌ 持有
是否可回滚 ❌ 不敢

👉 只能"干等"


4.2 CAP 角度再看 2PC

2PC 的取舍是:

  • 放弃可用性(A)

  • 换取强一致性(C)

当网络分区或协调者宕机时:

❌ 宁可阻塞

❌ 也不允许出现不一致


5. 2PC 的优缺点

优点:

  • 强一致性

  • 理论完备

  • 适合金融级事务

缺点:

问题 本质
阻塞 协调者单点
性能低 多次网络 + 日志 IO
扩展性差 参与者越多,失败概率越高

6. 为什么 2PC 没有被广泛使用?

不是它不好,而是它"太理想主义"

从论文视角看,2PC 非常优雅:

  • 协调者(Coordinator)全局视角

  • 参与者(Participant)严格执行

  • 只要协议走完,就能保证强一致

但问题是:

2PC 的前提条件,在现实系统中几乎从不成立。


6.1 2PC 的三个"理想前提"

  1. 协调者永远可靠

    在 2PC 中,协调者是"上帝节点"

    • 所有参与者都在等它的最终裁决

    • 它一旦挂掉:

      • 已锁资源的参与者 无事可做

      • 协议直接进入"悬而未决"状态

    现实世界:

    • JVM 会 OOM

    • Pod 会被 K8s 重启

    • 机器会宕机、磁盘会坏

    👉 单点协调者 = 单点灾难源

  2. 网络是稳定、可预测的

    2PC 极度依赖网络可靠性:

    • Prepare 请求要全部成功返回

    • Commit / Rollback 必须"明确送达"

    但现实是:

    • 网络延迟不可控

    • TCP 断连 ≠ 对方是否收到

    • 超时 ≠ 对方失败

    于是就会出现:

    • 协调者认为失败 → 回滚

    • 某个参与者其实已经提交成功

    👉 网络不可靠,让 2PC 的"确定性"失效

  3. 资源可以被长时间锁住

    2PC 的本质:

    先锁资源,再等所有人点头

    在高并发系统中,这意味着:

    • 数据库行锁 / 表锁长期占用

    • 事务执行时间被"最慢节点"拖死

    • 任何一个慢节点都会放大整体延迟

    结果就是:

    • TPS 急剧下降

    • 死锁概率上升

    • 系统吞吐量崩溃

    👉 在高并发系统中,2PC ≈ 主动制造性能瓶颈


6.2 2PC 的"致命工程缺陷"

把这些问题放到真实系统里,你会发现几个硬伤:

问题 工程后果
协调者单点 需要额外高可用设计
参与者阻塞 线程、连接、锁被长期占用
无法自动恢复 需要人工介入清理
延迟放大 P99 / P999 极不稳定

这也是为什么:

  • 数据库层面:只在极小范围内使用 2PC

  • 微服务层面:几乎全面弃用


6.3 工业界的选择:放弃"强一致",拥抱"可用性"

现实系统的核心目标不是:

"任何时刻都绝对一致"

而是:

系统不挂、能恢复、能自愈

于是工业界达成了一个共识:

牺牲瞬时一致性,换取最终一致性

常见做法包括:

  • 本地事务 + 消息通知

  • 可靠消息最终一致性

  • 补偿事务(TCC / Saga)

  • 幂等 + 重试 + 对账

它们的共同点是:

  • 不长时间锁资源

  • 允许短暂不一致

  • 失败可恢复

  • 系统整体可用


6.4 一句话总结

2PC 适合论文、实验室和小范围强一致场景
但不适合高并发、分布式、易失败的真实世界

也正因为如此:

分布式事务的主战场,早已从 2PC 转向"最终一致性方案"


总结

到这里,你应该自然产生这些疑问:

  • ❓ 能不能 不锁资源

  • ❓ 能不能 不用协调者阻塞等待

  • ❓ 能不能 通过业务补偿代替强回滚

这正是后续方案出现的原因:

方案 核心思想
3PC 降低阻塞
TCC 业务显式控制
Saga 反向补偿
本地消息表 数据最终一致
可靠消息 消息驱动事务

2PC 是分布式事务的"理论起点",
也是工业实践必须绕开的"性能天花板"。


第三部分:TCC ------ 把事务控制权从数据库交还给业务

如果说 2PC 是"让数据库帮我们保证分布式一致性",

那么 TCC (Try - Confirm/Cancel)的本质 是:

承认数据库做不到这一点,于是我们自己来做。
❗ 关键在于:TCC 是一套非常严谨、非常克制的业务事务模型


1. TCC 出现之前的核心问题

2PC 中,跨服务事务的问题不在于"一致性",而在于锁与阻塞

  • 锁在数据库层,不可控

  • 锁时间长,轻微慢事务拖垮系统

  • 协调者挂掉 → 所有资源阻塞

🔥 少量慢事务可能拖垮整个系统,这是互联网分布式环境的致命问题

问题归纳

❗ 有没有一种方式,既能保证一致性,又不把数据库锁死?

TCC 的答案

不要让数据库替你锁资源,而是让业务自己"显式管理资源状态"

知识点记忆

  • 2PC → 数据库锁资源 → 阻塞

  • TCC → 业务管理资源 → 弹性控制


2. TCC 核心思想:事务不是一瞬间完成的

单体事务模型:

text 复制代码
开始事务 → 修改数据 → 提交 / 回滚

在分布式系统中,这个模型不现实

TCC 核心承认的事实

❗ 跨服务事务,本质上是一个"过程",不是瞬间完成

三个业务阶段

阶段 真正含义 核心目的
Try 资源预留 能不能做?先占住资源
Confirm 正式提交 确认执行,把事务做完
Cancel 回滚 不做了,把资源还回去

⚠️ 关键点:

Try 不是"尝试执行",而是"抢占资格"


3. 完整库存场景理解 TCC

3.1 错误直觉

很多人以为:

Try 阶段只是预扣,库存值不变 → 并发仍然能读到旧库存

结果:

❌ 必然超卖
并非 TCC 错,而是设计错了 TCC


3.2 正确 TCC 库存模型

至少需要三个字段:

text 复制代码
stock
------
product_id
total_stock     // 总库存
available_stock // 可用库存
frozen_stock    // 冻结库存

并满足:

text 复制代码
available_stock + frozen_stock = total_stock

4. Try 阶段到底改没改数据库?

结论

❗ Try 一定会修改数据库,但只修改"业务可用资源"

不是 total_stock,而是 available_stock/frozen_stock


  1. 并发场景推演

    初始状态

    text 复制代码
    total = 10
    available = 10
    frozen = 0
  2. 请求 A:购买 6 件

    sql 复制代码
    UPDATE stock
    SET
    available_stock = available_stock - 6,
    frozen_stock = frozen_stock + 6
    WHERE
    product_id = 1
    AND available_stock >= 6;

    结果

    text 复制代码
    available = 4
    frozen = 6
  3. 请求 B:并发购买 6 件

    sql 复制代码
    available_stock >= 6 ❌ 不成立

    👉 Try 失败,自然挡住并发

知识点记忆

  • TCC 并发控制 不靠锁,靠原子条件更新 + 可用资源字段

5. 读旧数据问题解析

5.1 事务参与者

关心:

text 复制代码
available_stock >= 下单数量
  • 读的是 Try 之后的 available_stock

  • ✅ 不存在旧数据问题

5.2 用户查询接口

  • ❌ 不展示 total_stock / frozen_stock

  • ✅ 展示 available_stock

  • 用户看到的就是 Try 之后的结果

🔥 TCC 不存在"读旧数据"问题,只要设计正确


6. TCC 的"锁"在哪里?

对比项 数据库事务锁 TCC 冻结
锁的位置 数据库内核 业务字段
锁形式 行锁 / 表锁 状态位 / 数量字段
可见性 不可见 完全可见
生命周期 不可控 业务可控
是否可补偿

本质理解

🔥 TCC = 把隐式锁 → 显式状态,交给业务可控


7. Confirm 与 Cancel:事务真正结局

Confirm 阶段

text 复制代码
frozen_stock -= n
total_stock -= n
  • 真正数据提交

Cancel 阶段

text 复制代码
frozen_stock -= n
available_stock += n
  • 业务层回滚

知识点

  • Confirm / Cancel 才是 TCC 的最终结果
  • Try 只是资源占用

8. TCC 的三个隐藏难点

  1. 幂等性

    • Confirm / Cancel 可能被重复调用
    • 必须能重复执行而不出错 → 状态判断
  2. 空回滚

    • Cancel 先于 Try 到达
    • 必须允许什么都不做直接成功
  3. 悬挂问题

    • Cancel 执行完 → Try 又来了
    • Try 必须检查是否已 Cancel

9. Try 成功但 Confirm 不来怎么办?

现实系统一定会发生

解决方案

  • 冻结记录带时间戳

  • 定时任务扫描

  • 超时自动 Cancel

text 复制代码
冻结超过 30 分钟 → 自动释放

🔥 这是 TCC 的设计本意,不是异常


10. TCC 的适用场景与限制

适合

  • 库存冻结

  • 资金冻结

  • 支付扣款

  • 强一致性业务

不适合

  • 长流程(分钟 / 小时级)

  • 无法预留资源的场景

  • 简单 CRUD


11. 核心记忆总结

🔥 TCC 并不是数据库事务替代品,而是业务承担事务复杂性的一种方式

特点:

  1. 并发控制 → 原子更新 + 可用资源字段

  2. 避免阻塞 → 锁交给业务

  3. 异常处理 → Confirm / Cancel + 超时策略

  4. 幂等 & 状态机 → 保障可靠性


第四部分:Saga ------ 接受不一致,用时间换系统存活

如果说:

  • 2PC:一致性优先,系统其次

  • TCC:一致性和性能的平衡

那么 Saga 的哲学是:

系统活着,比数据立刻一致更重要


1. Saga 出现的根本原因:长事务 TCC 承受不了

在之前的章节,你已经理解了:

  • TCC 通过冻结资源保证一致性

  • 冻结本质是一种 业务级锁

问题来了:

❓ 如果这个"事务"持续 几分钟、几小时、甚至几天,系统该怎么办?

现实问题示例

场景 步长 时间
创建订单 写订单表
扣库存 减库存、冻结库存
支付 扣款、冻结资金
拣货 仓库操作 几小时
发运 物流配送 几天
用户签收 确认收货 几天

TCC 的限制

长时间冻结资源意味着:

  • 库存锁定几天 → 其他订单无法操作

  • 钱冻结几天 → 用户无法使用余额

  • 系统资源压力大 → 易堵塞

🔑 结论:TCC 不适合长事务场景


2. Saga 的核心思想:事务不是"一次性提交"

Saga 的第一性原理是:

分布式事务 = 一连串已经发生的事实

你无法让"已经发生的事情"消失,只能:

  1. 接受它

  2. 用另一个行为抵消它

原理解析

  • 2PC / TCC 的事务依赖锁或协调者保证"一致性瞬间达成"

  • 长事务会导致锁超时、冻结资源过久、系统压力过大

  • Saga 放弃瞬时一致,用 补偿事务(Compensation) 替代回滚

关键理解 :Saga 不是回滚旧事务,而是 执行新事务抵消影响


3. Saga 的基本模型

结构示意

text 复制代码
T1 → T2 → T3 → T4
↑    ↑    ↑
C1   C2   C3
  • T:正向事务

  • C:补偿事务

电商场景示例

正向事务 补偿事务
T1:创建订单 C1:取消订单
T2:扣库存 C2:恢复库存
T3:扣款 C3:退款
T4:发货 C4:拦截或退货

底层原理

  • 每个本地事务依然是单体事务,保持本地 ACID

  • Saga 将全局一致性拆解为 一系列本地事务 + 补偿

  • 不依赖锁或协调者长时间阻塞资源

记忆点 :Saga 的核心 = 顺序执行 + 补偿


4. Saga 与 TCC 的本质对比

对比点 TCC Saga
一致性 强一致 最终一致
是否冻结资源
事务时长 秒级 分钟 / 小时 / 天
回滚方式 Cancel(业务回滚) 补偿事务(新业务逻辑)
系统压力
并发控制 数据库锁 / 条件更新 业务状态校验

🔑 核心理解:Saga 用 允许短暂不一致系统可用性


可以在文档里加一些实际业务场景的例子,这样读者更容易理解两种 Saga 的差异。我帮你补充如下:


5. Saga 的两种实现方式

编排式 Saga(Orchestration)

  • 有中心协调器(Saga Coordinator)

  • 统一控制事务执行顺序

  • 失败时发起补偿事务

优点

  • 流程清晰

  • 易监控

  • 可追踪全局事务状态

缺点

  • 中心节点压力大

  • 中心故障需要额外容灾

  • 难以水平扩展

例子

电商下单流程:

  1. 用户下单 → Saga Coordinator 接收请求

  2. 扣减库存 → 扣款 → 创建物流订单(顺序控制)

  3. 如果扣款失败 → Saga Coordinator 发起补偿事务(回滚库存)

这里协调器负责整个流程的顺序控制和补偿逻辑,整个事务链条清晰可追踪。


事件驱动 Saga(Choreography)

  • 服务间通过事件通信(事件总线)

  • 无中心协调者

优点

  • 松耦合

  • 易扩展

  • 服务自治

缺点

  • 调试困难

  • 逻辑分散,事件追踪复杂

  • 容易出现事务漏补偿

例子

同样是电商下单流程:

  1. 下单服务发出 OrderCreated 事件

  2. 库存服务监听事件 → 扣减库存 → 发布 InventoryReservedInventoryFailed

  3. 支付服务监听 InventoryReserved → 扣款 → 发布 PaymentCompletedPaymentFailed

  4. 物流服务监听 PaymentCompleted → 创建物流订单

每个服务独立处理事件并发布结果,没有中心协调器。流程松耦合,容易扩展,但如果某个事件丢失或者处理失败,可能导致事务不一致,需要额外设计补偿机制。


6. Saga 的关键工程难点

难点 说明 解决方法
补偿不一定成功 已发生事实不可撤销 人工介入或业务逻辑补偿
幂等 & 重试 事件可能重复触发 补偿事务设计幂等、记录状态
中间态存在 系统短期内不一致 状态机 + 状态校验规则
并发问题 多事务同时执行可能冲突 业务约束:状态检查、顺序控制

原理解析

  • 补偿事务必须能安全重复执行 → 幂等性

  • 系统允许短期不一致 → 最终一致性

  • 并发控制交给业务逻辑 → 不依赖数据库锁


7. Saga 的适用场景

场景类型 特点
长流程事务 跨系统、分钟至天级事务
无法冻结资源 库存、资金、长时间外部操作
最终一致性可接受 电商订单、支付 + 物流、积分兑换
高可用要求 系统不能因为事务阻塞而瘫痪

不适用

  • 短事务要求强一致性 → 2PC / TCC 更优

  • 无法设计补偿逻辑的场景 → Saga 不适用


8. 工程实践技巧

  1. 幂等设计:确保补偿或重试不会重复扣款或重复库存调整

  2. 状态机设计:用订单/业务状态约束操作,防止非法跳转

  3. 日志/事件追踪:记录每步事务状态,便于问题排查

  4. 补偿策略:自动 vs 人工,分清可自动补偿和需要人工介入的步骤

  5. 监控告警:事务长时间未完成 → 警报或触发补偿


9. 总结与记忆点

  1. Saga 核心思想:放弃瞬时一致性 → 接受不一致 → 用补偿事务保证最终一致

  2. TCC vs Saga:TCC 锁资源、强一致;Saga 不锁资源、最终一致

  3. 事务粒度:Saga 拆分成多个本地事务 → 每个事务独立保持 ACID

  4. 工程难点:幂等、补偿失败、中间态、并发控制

  5. 适用场景:长流程事务、无法冻结资源、系统高可用、可接受最终一致性

🔥 核心哲学 :系统活着,比数据立刻一致更重要。

选择分布式事务方案 = 在一致性、性能、复杂性之间做取舍


第五部分:可靠消息 / 事务消息 ------ 把一致性交给时间

如果说:

  • 2PC 是"强一致但脆弱"

  • TCC 是"强一致但复杂"

  • Saga 是"接受不一致但仍然痛苦"

那么 可靠消息的哲学是:

只要最终一致即可,过程不一致是常态

这不是妥协,而是对现实世界的尊重。


1. 先抛结论:为什么消息几乎是最终答案?

在真实的分布式系统中,最致命的三件事是:

  1. 同步调用阻塞

  2. 中央协调节点挂掉

  3. 跨服务锁资源

这些问题并不是"实现不好",而是系统规模一旦变大就必然出现

而消息天然具备的特性,正好对症下药:

  • 异步:不等结果,先往前走

  • 解耦:服务之间不再直接"绑死"

  • 可重试:失败不是终点,只是延后

  • 可削峰:流量不再同时压垮系统

👉 所以你会发现一个规律:

几乎所有大规模系统,最终都会演进到「本地事务 + 消息」

这不是潮流,是幸存者路径。


本节记忆总结

  • 同步 ≠ 安全,只是看起来简单

  • 消息不是为了"方便",而是为了活下来

  • 系统越大,越依赖异步


2. 一个最真实的业务问题(几乎人人都踩过)

场景:下单 + 扣库存

text 复制代码
服务 A:订单服务(写订单表)
服务 B:库存服务(扣库存)

这是所有电商、内容、课程、权益系统的基础模型。


最天真的实现(必出问题)

text 复制代码
1. 写订单
2. 调库存接口扣库存

你可能觉得"逻辑没问题",但现实会告诉你:

  • 库存扣了,订单写失败?

  • 订单写了,库存扣失败?

  • 网络抖了一下,结果到底成功没?

👉 问题不是"怎么兜底"

👉 问题是:你根本不知道现在是什么状态


本节记忆总结

  • 分布式系统中,"不知道"本身就是一种失败

  • 同步调用只是在掩盖不确定性


3. 为什么"数据库事务 + 发消息"一定会失败?

很多人第一反应是:

text 复制代码
BEGIN;
INSERT INTO order;
SEND MQ MESSAGE;
COMMIT;

看起来很合理:

"要么都成功,要么都失败"。

但现实是:

数据库事务 和 MQ 没有共同的事务上下文

它们属于两个完全不同的世界。


一定会发生的失败场景

  1. DB 提交成功,消息没发出去

    • 下游永远不知道这笔订单
  2. 消息发出去了,DB 回滚了

    • 下游收到一条"幽灵消息"

👉 这不是 Bug,而是系统边界问题


本节记忆总结

  • 跨系统不存在"天然原子性"

  • 只要不是同一个资源管理器,就不存在真正的事务


4. 可靠消息的核心模式:本地事务 + 消息表(关键转折)

这是整个分布式事务体系里最重要的一步


1️⃣ 核心思想(一句话)

把"发消息"也当成一条业务数据

既然消息不可靠,那就先别发,先把"要发什么"记录下来。


2️⃣ 表结构示意(极简但够用)

text 复制代码
order
----
id
status

message_outbox
----
msg_id
topic
payload
status   // NEW / SENT

这张 message_outbox 表,就是可靠性的根源


3️⃣ 正确的执行流程(必须理解透)

Step 1:本地事务(原子保证)
text 复制代码
BEGIN;
INSERT order;
INSERT message_outbox(status = NEW);
COMMIT;

✔️ 订单和消息记录要么一起成功

✔️ 要么一起失败

此时还没有真正发消息


Step 2:异步投递(解耦关键)
  • 后台线程 / 定时任务扫描 message_outbox
  • NEW 状态的消息发送到 MQ
  • 发送成功 → 标记为 SENT

业务线程已经结束,不再被阻塞。


本节记忆总结

  • 可靠性的本质:先落盘,再异步
  • 消息表 = 自己的"消息保险箱"

5. 这套模型为什么"天然可靠"?

我们用最残酷的方式看失败。


场景 1:事务提交失败

  • 订单没写
  • 消息没记录

👉 等价于:什么都没发生


场景 2:事务成功,但服务宕机

  • 订单在
  • 消息在
  • 还没来得及发

服务重启后:

  • 扫描消息表
  • 继续发送

👉 消息不会丢


场景 3:消息发出,但 ACK 丢失

  • MQ 实际已收到
  • 本地仍是 NEW
  • 会再次发送

👉 必然重复

所以结论只有一个:

消费端必须幂等


本节记忆总结

  • 不丢消息 ≠ 不重复消息
  • 重复是"设计结果",不是异常

6. 那"消息重复"怎么办?(必须吃透)

核心认知

消息系统默认就是「至少一次」

任何"只消费一次"的幻想,都会在规模化后破灭。


常见幂等实现方式

  1. 唯一业务 ID

    • 订单号、流水号
  2. 去重表

    • 消费过就记下来
  3. 状态机校验

    • 已完成状态直接忽略

👉 幂等不是 MQ 的责任,是业务设计的一部分


本节记忆总结

  • 没有幂等,就没有可靠消息
  • 幂等 = 把"重复"当正常情况处理

可以,把你的 RocketMQ 事务消息示意改为 RabbitMQ 版本,思路是一样的:都是"消息表 + 本地事务 + 回查/补偿",只不过 RabbitMQ 自身没有内置事务消息机制,需要自己实现半消息/确认 + 状态回查。改写如下示例:


7. 事务消息(以 RabbitMQ 思想为例)

事务消息并不是新理论,而是:

"消息表模式"的中间件化实现


事务消息流程(高度抽象)

  1. 保存半消息到数据库(消息未投递到 RabbitMQ)

  2. 执行本地事务(如更新业务表)

  3. 事务成功 → 投递消息到 RabbitMQ

    事务失败 → 删除半消息 / 不投递

  4. 异步回查事务状态(可用定时任务扫描消息表,重试投递或补偿)

text 复制代码
[业务库操作] ---> 本地事务成功 ---> 投递消息 ---> MQ 消费者消费
      |                                 ^
      |--- 本地事务失败 ----------------|

👉 消息是否可见,取决于本地事务是否成功


本质没有变

  • 只是把:

    • 消息表

    • 状态回查

    • 重试逻辑

交给了应用层 + RabbitMQ 托管。


本节记忆总结

  • 事务消息不是"魔法"

  • 只是把土方案标准化

  • 与 RocketMQ 不同,RabbitMQ 没有内置事务消息机制,需要自己实现"半消息 + 回查"


8. 可靠消息 vs TCC vs Saga(最终对照)

维度 可靠消息 TCC Saga
本质目标 消息可靠投递 / 最终送达 强一致性,补偿可选 最终一致性,补偿必须
事务粒度 单条消息 业务操作 业务操作序列
是否全局锁 ❌ 不锁业务资源 ✅ Try 阶段可锁资源 ❌ 不锁,依赖补偿
是否有全局共识阶段 ❌ 没有 ✅ 有(Try / Confirm / Cancel) ❌ 没有,依靠补偿
失败处理 重试 / 死信 / 补偿可选 回滚(Cancel) 执行补偿事务
一致性保证 最终一致性(消息投递) 强一致性(Try + Confirm / Cancel) 最终一致性(补偿事务收敛)
典型场景 MQ 事务消息、可靠消息表模式 金融交易、库存扣减 订单履约、长流程审批、跨系统任务
实现复杂度 中(依赖 MQ + 状态回查) 高(每个业务操作需设计 Try/Confirm/Cancel) 中高(需要流程编排 + 补偿事务)
时效性 高(消息投递异步) 中低(可能占用锁,阻塞 Try 阶段) 中(依赖流程执行和补偿)

👉 消息是默认选项,TCC 是少数特权


解释核心差异

  1. 可靠消息
  • 其实是 消息一致性工具,保证消息不会丢、不会重复。

  • 不直接保证业务操作原子性,只是把"消息投递和本地事务尽量结合起来"。

  • 常用方式:

    • 消息表模式 + 定时扫描(RabbitMQ / RocketMQ 可实现)

    • MQ 事务消息(RocketMQ 内置半消息 + 回查)

  • ✅ 适合 异步解耦 + 最终一致性

  • ❌ 不是严格意义上的分布式事务

  1. TCC
  • Try / Confirm / Cancel 三阶段

  • 核心是 在提交前形成全局共识

  • ❌ 不能跨长时间(资源被锁定)

  • ✅ 强一致性,适合短事务场景

  1. Saga
  • 核心是 业务事务序列 + 补偿事务

  • ❌ 没有全局锁,也没有共识阶段

  • ✅ 能跨天/跨系统/人工参与

  • ✅ 适合长事务 / 最终一致性场景


一句话总结

  • 可靠消息 = "保证消息送达",不保证业务原子性

  • TCC = "严格一致性",资源锁定 + Try/Confirm/Cancel

  • Saga = "长事务最终一致性",依赖补偿事务收敛


9. 可靠消息的隐性成本

1️⃣ 延迟

  • 一致性是"迟到的正确"
  • 不适合强实时场景

2️⃣ 状态复杂度

  • 订单状态
  • 消息状态
  • 补偿状态

系统复杂度真实存在。


3️⃣ 运维成本

  • 消息堆积
  • 重试风暴
  • 死信处理

消息不是银弹,只是可控的麻烦


本节记忆总结

  • 没有"零成本一致性"
  • 只是把风险换了种形态

第六部分:如何在真实系统中选择分布式事务方案

真正成熟的工程能力,不是知道所有方案,
而是知道:在什么地方,必须妥协。

一条连续的发展线:

上一阶段解决了什么 → 暴露了什么问题 → 下一阶段为什么出现

阶段 出现原因(上一阶段的问题) 底层原理 / 机制 优点 缺点 优化方案 优化后仍存在问题 适用场景
单体事务 业务集中在单体系统、单数据库 ACID;undo/redo log;锁/MVCC;WAL 实现简单;强一致性 只能单库;无法跨服务 无需优化 一旦拆库拆服务,事务失效 单体应用;金融单库核心账务
2PC 单体事务无法跨库/跨服务 Coordinator + Participant;Prepare / Commit;持久化日志;资源锁定 强一致性;支持跨库 阻塞;性能低;强依赖网络与协调者 引入 预提交 网络分区仍可能不一致;通信成本高 核心金融系统;低并发高一致性
3PC 2PC 协调者宕机导致阻塞 CanCommit → PreCommit → DoCommit;超时自决 减少阻塞;参与者可超时处理 通信轮次多;复杂度高 放弃绝对强一致,引入业务级事务 网络分区仍不可靠;工程复杂 银行核心交易;高可靠系统
TCC 3PC 在微服务下性能和复杂度不可接受,但仍需强一致性 Try 冻结资源;Confirm 提交;Cancel 释放 接近强一致性;业务可控 开发成本高;资源长期冻结 自动化 TCC 框架;精细化冻结 高并发下资源压力大 资金;高价值库存;账户系统
Saga(编排式) TCC 过重,微服务下阻塞成本高 本地事务 + 补偿事务;中心协调器;状态机 非阻塞;高性能;流程清晰 补偿复杂;短时不一致 状态机固化流程;幂等与重试 补偿并非严格逆操作 电商订单;库存;支付流程
Saga(事件式) 编排式中心协调器成为瓶颈 事件驱动;消息队列;服务自治 去中心化;松耦合;易扩展 消息乱序/丢失;排错困难 事件追踪;可观测性建设 调试复杂;一致性可见性差 大型微服务体系
消息驱动最终一致性 Saga 在海量系统下仍有复杂度 本地事务 + 可靠消息;幂等消费;重试 高吞吐;高可用;高扩展 只能最终一致;补偿链路长 业务对账;异步修复;监控告警 无法实时强一致 海量订单;日志;社交系统

1. 先给你一个残酷但真实的结论

99% 的业务,其实根本不需要"分布式事务"

你之所以"感觉需要",往往是因为:

  • 服务拆分过早
  • 边界没想清楚
  • 想让系统"看起来很干净"

2. 为什么"单体事务"能解决的问题,千万别拆?

单体事务的真正优势(很多人低估了)

  • ACID 是数据库几十年工程沉淀
  • 日志、锁、恢复、隔离级别全都帮你做好了
  • 出问题可以靠 DBA、备份、回滚

👉 能在一个库里完成的事,千万别拆出去


3. 什么时候你"被迫"进入分布式事务?

只有当以下情况同时成立

  1. 数据量 / 组织结构 / 技术边界 必须拆库拆服务
  2. 操作横跨多个独立服务
  3. 不能接受随意失败或不一致

👉 你才真正进入了分布式事务领域


4. 一套真实可用的"选择决策树"**

请你把下面这套逻辑记成条件反射


第一步:能不能"异步最终一致"?

如果可以:

👉 优先:可靠消息 / 事务消息

  • 电商
  • 内容系统
  • 用户行为
  • 统计数据

为什么?

  • 简单
  • 稳定
  • 扩展性最好

第二步:是否涉及"钱 / 库存 / 额度"?**

如果是:

问下一个问题:

能不能接受"冻结"?


第三步:能不能冻结资源?

如果能冻结(秒级):

👉 TCC

  • 资金冻结
  • 库存锁定
  • 配额预占

如果不能冻结或时间太长:

👉 Saga


5. 一个完整的现实系统是怎么混用的?**

你以为系统只能选一种?

现实是:同时存在 3~4 种事务模型

电商真实拆解示意:

模块 方案
下单 → 写订单 本地事务
下单 → 扣库存 TCC
下单 → 发通知 消息
履约 → 发货 Saga
统计 → 报表 最终一致

👉 没有银弹,只有组合拳


6. 最常见、最致命的 5 个误区**

❌ 误区 1:追求"全局强一致"

现实:

你会得到一个慢、脆、贵的系统


❌ 误区 2:到处用 TCC

现实:

  • 代码爆炸
  • 测试地狱
  • 运维崩溃

❌ 误区 3:把 MQ 当数据库

现实:

  • 状态丢失
  • 排查困难

❌ 误区 4:忽略幂等和重试

现实:

第一次不出问题,

第 100 次必出事故


❌ 误区 5:设计时不考虑失败

现实:

分布式系统中,失败才是常态


7. 你必须形成的三种"工程直觉"

所有远程调用都可能失败

所以:

  • 必须可重试
  • 必须幂等

所有一致性都是"设计出来的"

不是数据库帮你保证

是你用 状态 + 规则 + 补偿 保证


数据不一致 ≠ 系统失败

真正的失败是:

系统无法自愈


8. 如果让你"从零设计一个系统",该怎么做?

一个成熟的步骤是:

  1. 先单体

  2. 用数据库事务兜底

  3. 只在瓶颈处拆服务

  4. 拆了之后:

    • 默认消息

    • 关键点 TCC

    • 长流程 Saga


9. 你现在应该真正"吃透"的分布式事务观

请你记住这段话:

🔥 分布式事务不是技术问题,是"选择问题"

🔥 一致性不是免费的,它用性能、复杂度、可用性换

🔥 工程的最高境界,是知道哪里"不值得完美"
把简单问题留给数据库,
把复杂问题交给业务,
把不可控问题交给时间。


相关推荐
java1234_小锋3 小时前
Spring里AutoWired与Resource区别?
java·后端·spring
风象南3 小时前
Spring Boot 定时任务多实例互斥执行
java·spring boot·后端
崎岖Qiu3 小时前
【深度剖析】:结合 Spring Bean 的生命周期理解 @PostConstruct 的原理
java·笔记·后端·spring·javaee
毕设源码-郭学长3 小时前
【开题答辩全过程】以 基于Springboot旅游景点管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
Coder个人博客3 小时前
Linux6.19-ARM64 mm Makefile子模块深入分析
linux·安全·车载系统·系统架构·系统安全·鸿蒙系统·安全架构
方安乐4 小时前
杂记:Quart和Flask比较
后端·python·flask
qq_12498707535 小时前
基于SpringBoot的闪电队篮球俱乐部管理系统的设计与开发(源码+论文+部署+安装)
java·数据库·spring boot·后端·spring·毕业设计·计算机毕业设计
哪里不会点哪里.5 小时前
Spring Boot 自动装配原理深度解析
java·spring boot·后端
是三好5 小时前
javaSE
java·后端·spring