Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

后端工程 / Backend Engineering

1. Backend 后端技术全景学习路线 / A Complete Backend Engineering Learning Path

2. 分布式系统——为什么单体时代过去了 / Distributed Systems Beyond the Monolith

3. 并发编程——为什么你的库存总是扣成负数 / Concurrency Control and Inventory Consistency

4. MySQL 优化——为什么你的查询总是那么慢 / MySQL Query and Storage Optimization

5. 架构模式——什么时候该用 CQRS / Architecture Patterns and When to Use CQRS

6. 系统设计——如何设计一个每秒 10 万订单的秒杀系统 / Designing a High-Throughput Flash-Sale System

7. 数据库工程化——在线表结构变更怎么不停服 / Database Engineering and Online Schema Changes

8. 可观测性——出了问题怎么快速定位 / Observability and Rapid Production Diagnosis

本页目录

分布式系统——为什么单体时代过去了 / Distributed Systems Beyond the Monolith ​

📅 创建时间:2026-05-08 🏷️ 标签:#分布式 #CAP #BASE #Raft #Paxos #分布式ID #分布式事务 📚 前置知识:[[00-backend-overview]] [[01-redis-deep]] [[02-mq-kafka]] 📚 相关知识:[[/03-web/06-databases-and-data-access/06-kv-embedded]](Raft 协议) [[/03-web/06-databases-and-data-access/07-distributed-sql]](NewSQL)


场景:你的单体系统撑不住了 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  双十一前一个月,你的单体电商系统开始出问题:              │
│                                                             │
│  问题 1:数据库 CPU 100%,单表 5000 万行                  │
│  → 加索引没用,数据量太大了                                │
│                                                             │
│  问题 2:订单服务和用户服务"互相影响"                     │
│  → 订单服务挂了 → 用户服务也挂了(共享进程)              │
│                                                             │
│  问题 3:发布一次需要停机 2 小时                           │
│  → 改一行代码,全系统回归测试                              │
│                                                             │
│  CTO 说:拆!                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

分布式系统解决了单体的问题,但也带来了新的问题。这一章,我们理解分布式系统最核心的挑战。


第1节:CAP 定理——分布式系统的不可能三角 ​

问题抽象 ​

三个人分两间教室考试(两个节点)。突然停电了(网络分区)。

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  选择 A:发卷子(一致性)                                  │
│  停电了,发不了卷子 → 学生等(不可用)                   │
│                                                             │
│  选择 B:发备用卷子(可用性)                             │
│  备用卷和正式卷不一样 → 数据不一致                        │
│                                                             │
│  鱼和熊掌不可兼得。                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11

推演:CAP 到底是什么 ​

┌─────────────────────────────────────────────────────────────┐
│                 CAP 三角:三个只能同时满足两个              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      Consistency (C)                       │
│                           ▲                                │
│                          /│\                               │
│                         / │ \                              │
│                        /  │  \                             │
│                       /   │   \                            │
│                      /    │    \                           │
│                     ▼─────┼─────▼                          │
│          Availability ◄───┴───► Partition Tolerance       │
│           (可用性)         (分区容错)                      │
│                                                             │
│  C + A(放弃 P):传统单机数据库                         │
│  → 不存在分区(单台机器),所以 C 和 A 可以同时满足       │
│  → MySQL、PostgreSQL、Redis(单机模式)                  │
│                                                             │
│  C + P(放弃 A):强一致系统                              │
│  → 分区发生时,系统停止服务(返回错误)                   │
│  → ZooKeeper、etcd、CockroachDB(强一致模式)            │
│                                                             │
│  A + P(放弃 C):最终一致系统                           │
│  → 分区发生时,系统继续服务,但数据可能不一致             │
│  → Cassandra、DynamoDB、MongoDB(副本集默认模式)         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

常见误解澄清 ​

❌ 误解:CAP 意味着"三选二"
✅ 真相:正常运行时(无分区),系统可以同时满足 C 和 A。
        只有发生分区时,才被迫选择。

❌ 误解:分布式系统一定要选 AP 或 CP
✅ 真相:P(分区容错)是分布式系统的固有属性,无法放弃。
        所以实际是:在 C 和 A 之间选择。

❌ 误解:MySQL 比 Redis 更"一致"
✅ 真相:单机的 MySQL 因为没有分区,可以同时满足 C 和 A。
        但它不是分布式系统。
1
2
3
4
5
6
7
8
9
10
11

第2节:BASE 理论——现实的妥协 ​

问题抽象 ​

MySQL 的强一致性很好,但在分布式系统下代价太高(2PC 要锁资源)。

现实中:我们愿意接受"最终一致",只要系统最终是对的就行。

BASE 的含义 ​

┌─────────────────────────────────────────────────────────────┐
│                 BASE 理论三要素                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Basically Available(基本可用):                          │
│  系统出现故障时,允许降级服务,但核心功能可用。            │
│  例:Redis 从节点挂了,主节点继续服务                      │
│                                                             │
│  Soft state(软状态):                                    │
│  系统状态在不同节点间同步时,允许存在中间状态。            │
│  例:主从复制的延迟期间,主从数据可能短暂不一致            │
│                                                             │
│  Eventually consistent(最终一致):                        │
│  系统在故障恢复后,经过一段时间,数据会达到一致。         │
│  例:MySQL 主从复制完成后,从库数据和主库一致              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

对比:
强一致(MySQL 事务):写入后,所有人立即看到同样的数据
最终一致(BASE):写入后,经过一段时间,所有人看到同样的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

一致性模型对比 ​

┌─────────────────────────────────────────────────────────────┐
│              一致性模型从强到弱的排序                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  强一致 → 顺序一致 → 因果一致 → 最终一致 → 弱一致         │
│    ↑                                                           │
│  MySQL          Redis    分布式 KV   Cassandra            │
│  事务           主从同步  主从最终一致                      │
│                                                             │
│  强一致:所有节点同一时刻看到同样的数据                     │
│  顺序一致:所有节点按相同顺序看到所有操作                  │
│  因果一致:因果相关的操作按顺序,不同操作可并发            │
│  最终一致:经过足够时间,所有节点数据一致                  │
│                                                             │
│  越强的一致性 = 越多的协调 = 越低的性能和可用性           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第3节:Raft 共识算法——如何让多个节点达成一致 ​

场景:三个人决定谁当队长(选主问题) ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  三个人分别在北京、上海、广州(三台服务器)                │
│                                                             │
│  正常情况:多数派决定(2/3 同意即通过)                  │
│                                                             │
│  网络分区情况:                                            │
│  北京 ←→ 上海 | 广州(广州和北京、上海断开了)            │
│                                                             │
│  北京 + 上海:多数派(可以选主、可以写数据)              │
│  广州:少数派(不能选主、不能写数据)                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

Raft 选主流程 ​

┌─────────────────────────────────────────────────────────────┐
│                    Raft 选主流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  初始状态:三台都是 Follower                                │
│                                                             │
│  Step 1: 选举超时(每个节点随机 150-300ms)               │
│  节点 A 超时 → 成为 Candidate → 开始选举                   │
│                                                             │
│  Step 2: 投票请求                                          │
│  A ──投票请求──▶ B (A 有更新的日志) → B 同意 ✅            │
│  A ──投票请求──▶ C (A 有更新的日志) → C 同意 ✅            │
│                                                             │
│  Step 3: 获得多数票 → 成为 Leader                          │
│  A 获得 2 票(自己 + B)→ 成为 Leader                     │
│                                                             │
│  Step 4: 心跳保活                                          │
│  A 定期发心跳给 B、C → "我还是 Leader,别重新选举"        │
│                                                             │
│  如果 A 挂了:                                            │
│  B、C 的选举超时触发 → 其中一个成为 Candidate → 重新投票   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Raft vs Paxos ​

┌─────────────────────────────────────────────────────────────┐
│                 Raft vs Paxos 对比                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Paxos(Leslie Lamport,1998):                          │
│  • 理论证明完整,实际系统难以直接实现                      │
│  • Multi-Paxos 优化复杂                                    │
│  • Zookeeper 使用的是 Paxos 的变种(ZAB)                 │
│                                                             │
│  Raft(Diego Ongaro,2014):                             │
│  • 从 Paxos 简化而来,核心思想相同                        │
│  • 更好理解,更容易实现                                    │
│  • etcd、CockroachDB、TiKV 使用 Raft                      │
│                                                             │
│  面试中问 Paxos:理解 Basic Paxos 的两个阶段              │
│  • Prepare:提案编号 N,询问"我可以用 N 吗?"           │
│  • Accept:多数派同意后,确认"就用 N 了"                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第4节:分布式 ID 生成——如何生成不重复的 ID ​

场景:订单 ID 不能重复 ​

单体系统:直接用数据库自增 ID
分布式系统:多个节点同时生成 ID → 可能冲突

雪花算法(Snowflake):
  时间戳(41 位)    机器 ID(10 位)  序列号(12 位)
  |________________|_____|______|
  2026-05-08     节点 5     0-4095

  每毫秒每个节点可以生成 4096 个不重复 ID
  理论上:69 年内不重复
1
2
3
4
5
6
7
8
9
10

雪花算法实现 ​

python
import time

class Snowflake:
    def __init__(self, worker_id):
        self.worker_id = worker_id
        self.sequence = 0
        self.last_timestamp = -1
        self.epoch = 1704067200000  # 2024-01-01

    def _current_millis(self):
        return int(time.time() * 1000)

    def next_id(self):
        ts = self._current_millis()

        if ts == self.last_timestamp:
            self.sequence = (self.sequence + 1) & 4095  # 序列号轮回
            if self.sequence == 0:
                # 同一毫秒内序列号用完了,等下一毫秒
                while ts <= self.last_timestamp:
                    ts = self._current_millis()
        else:
            self.sequence = 0

        self.last_timestamp = ts

        # 组装 ID:时间差 << 22 | worker_id << 12 | sequence
        return ((ts - self.epoch) << 22) | (self.worker_id << 12) | self.sequence
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

分布式 ID 方案对比 ​

┌─────────────────────────────────────────────────────────────┐
│              分布式 ID 方案对比                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 方案          │ 优点           │ 缺点                  │
│  ├───────────────┼────────────────┼───────────────────────┤
│  │ UUID          │ 简单,不依赖    │ 无序,36 字符太长    │
│  │               │ 任何服务        │                       │
│  ├───────────────┼────────────────┼───────────────────────┤
│  │ 雪花算法      │ 有序,趋势递增  │ 依赖时钟(时钟回拨)│
│  │ (Snowflake)   │ 性能高         │ 依赖机器 ID 配置     │
│  ├───────────────┼────────────────┼───────────────────────┤
│  │ 数据库自增    │ 简单,绝对有序  │ 性能有限,依赖单点   │
│  ├───────────────┼────────────────┼───────────────────────┤
│  │ 百度 UidGenerator│ 趋势递增,   │ 实现复杂              │
│  │               │ 时钟友好       │                       │
│  └───────────────┴────────────────┴───────────────────────┘
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第5节:分布式事务——跨系统的原子性 ​

场景:下单 + 扣库存 + 扣余额,必须同时成功或失败 ​

单体系统:一个事务搞定

分布式系统:
  服务 A(订单服务)──INSERT orders──▶ MySQL
  服务 B(库存服务)──DECR stock──▶ Redis
  服务 C(支付服务)──DECR balance──▶ 账户数据库

  如果 B 成功了,C 失败了 → 库存扣了但钱没扣 → 数据不一致!
1
2
3
4
5
6
7
8

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

┌─────────────────────────────────────────────────────────────┐
│                 2PC 两阶段提交                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  协调者(事务管理器):                                    │
│                                                             │
│  Phase 1: Prepare                                         │
│  协调者 ──▶ 服务 A: "你们准备好了吗?"                   │
│  协调者 ──▶ 服务 B: "你们准备好了吗?"                   │
│  协调者 ──▶ 服务 C: "你们准备好了吗?"                   │
│  A: 准备好了(锁定资源)                                  │
│  B: 准备好了(锁定资源)                                  │
│  C: 还没准备好                                            │
│                                                             │
│  Phase 2: Commit / Rollback                               │
│  协调者 ──▶ C: "回滚"                                    │
│  协调者 ──▶ A: "提交"                                    │
│  协调者 ──▶ B: "提交"                                    │
│                                                             │
│  问题:                                                   │
│  ❌ 单点故障:协调者挂了,所有服务资源锁定                │
│  ❌ 同步阻塞:Prepare 阶段所有服务阻塞                    │
│  ❌ 数据不一致:部分服务提交成功,部分回滚                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

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

┌─────────────────────────────────────────────────────────────┐
│                 TCC 模式                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Try:预留资源(不真正扣减)                               │
│  A: UPDATE orders SET status='pending'     ← 预占订单      │
│  B: Redis: DECRBY stock_pending 1          ← 预占库存      │
│  C: UPDATE accounts SET frozen=frozen+100 ← 冻结余额      │
│                                                             │
│  Confirm:确认执行(真正扣减)                             │
│  A: UPDATE orders SET status='confirmed'                  │
│  B: Redis: DECRBY stock 1; DECRBY stock_pending 1        │
│  C: UPDATE accounts SET balance=balance-100, frozen=frozen-100│
│                                                             │
│  Cancel:取消执行(回滚预留)                              │
│  A: DELETE orders WHERE status='pending'                  │
│  B: Redis: INCRBY stock_pending 1                         │
│  C: UPDATE accounts SET frozen=frozen-100                 │
│                                                             │
│  优点:                                                  │
│  ✅ 不用锁定资源(Try 阶段只是预留)                       │
│  ✅ 异步执行 Confirm/Cancel                               │
│                                                             │
│  缺点:                                                  │
│  ❌ 业务侵入性强:每个业务都要写 Try/Confirm/Cancel       │
│  ❌ 空回滚:Try 失败了,但 Cancel 仍被执行                 │
│  ❌ 悬挂:Confirm 超时失败,但实际执行成功了              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

方案三:Saga 模式(异步补偿) ​

┌─────────────────────────────────────────────────────────────┐
│                 Saga 模式(编排型)                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  适用场景:业务流程长,涉及多个服务,无法锁定资源          │
│                                                             │
│  Saga 编排器:                                            │
│                                                             │
│  Step 1: 扣库存(服务 A)                                 │
│  Step 2: 创建订单(服务 B)← 成功了                       │
│  Step 3: 扣余额(服务 C)← 失败了!                       │
│                                                             │
│  补偿:                                                  │
│  Step 4: 取消订单(服务 B 回滚)                          │
│  Step 5: 恢复库存(服务 A 回滚)                          │
│                                                             │
│  Saga vs TCC:                                            │
│  • TCC:同步两阶段,Try 阶段锁定资源                       │
│  • Saga:异步多阶段,每个步骤都可以正向或逆向              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

方案四:本地消息表(最实用) ​

┌─────────────────────────────────────────────────────────────┐
│                 本地消息表方案                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  核心思路:把消息先存在本地数据库,用数据库事务保证原子性  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │ BEGIN TRANSACTION                                   │  │
│  │  INSERT INTO orders (...) VALUES (...)              │  │
│  │  INSERT INTO outbox (id, topic, payload, status)    │  │
│  │    VALUES (1, 'stock', '{"product_id":1}', 'pending')│  │
│  │ COMMIT                                             │  │
│  │                                                     │  │
│  │ -- 订单和消息条目在一个事务中,要麼一起成功         │  │
│  │ -- 要麼一起失败                                    │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  后台任务:扫描 outbox 表,发送到 MQ,标记 status='sent'   │
│                                                             │
│  优点:                                                  │
│  ✅ 实现简单,不依赖分布式事务                            │
│  ✅ 数据库事务保证原子性                                  │
│  ✅ 可结合 Canal 监听 binlog 更优雅                       │
│                                                             │
│  缺点:                                                  │
│  ❌ 有短暂不一致(消息发送有延迟)                        │
│  ❌ 需要额外的定时任务扫描 outbox                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

升华:分布式事务选型 ​

┌─────────────────────────────────────────────────────────────┐
│              分布式事务选型决策树                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  业务场景:                                               │
│  ├── 扣库存 + 下订单(简单两方事务)                     │
│  │   └── 本地消息表(最简单,推荐)                       │
│  │                                                          │
│  ├── 涉及支付/金融(强一致性要求)                         │
│  │   └── RocketMQ 事务消息 / Seata AT 模式                 │
│  │                                                          │
│  ├── 业务流程长,涉及 3+ 服务(长流程)                   │
│  │   └── Saga 编排 / Seata Saga 模式                      │
│  │                                                          │
│  └── 需要锁定资源(高并发库存抢券)                       │
│      └── TCC / Seata XA(但性能差)                       │
│                                                             │
│  一句话总结:                                              │
│  大部分互联网场景,本地消息表是成本最低、效果最好的方案。  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ Seata / Saga 框架的具体配置和 API
✅ 2PC / 3PC 的协议细节
✅ UidGenerator / Leaf 的部署配置

必须理解:
🔴 CAP 定理和 BASE 理论的核心区别
🔴 Raft 选主的基本流程(不需要背细节)
🔴 为什么 2PC 不适合高并发场景
🔴 TCC / Saga / 本地消息表的适用场景
🔴 分布式 ID 的几种方案和各自优缺点
1
2
3
4
5
6
7
8
9
10
11

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇1. Backend 后端技术全景学习路线 / A Complete Backend Engineering Learning Path
下一篇3. 并发编程——为什么你的库存总是扣成负数 / Concurrency Control and Inventory Consistency

持续记录,持续成长

Copyright © Tidenflow