这是分布式系统的"理论物理"。理解了这三块,再看 ZooKeeper、etcd、TCC、Saga、Kafka 副本机制都能从根上看穿。
一、为什么需要分布式系统?
单机的极限:
- 性能:单机 CPU、内存、磁盘有上限
- 容量:TB 级数据单机放不下
- 可用性:单机宕机 = 服务全挂
- 地域:跨地域用户要就近访问
分布式系统就是用一群机器协作干活,但代价是引入了新难题:节点会失败、网络会延迟和分区、时钟不同步、消息会重复或乱序。
Leslie Lamport 名言:分布式系统就是当一台你不知道存在的机器宕机时,让你的电脑也无法使用的系统。
二、CAP 定理:分布式系统的"测不准原理"
2.1 三个属性
- C (Consistency) 一致性:所有节点同一时刻看到相同数据(强一致 / 线性一致)
- A (Availability) 可用性:每个请求都能在合理时间内返回结果(不一定是最新的)
- P (Partition tolerance) 分区容错性:网络分区发生时系统仍能继续运行
2.2 核心命题
分区发生时,C 和 A 只能二选一。
关键澄清:CAP 不是"三选二",而是 P 是必选项(网络一定会出问题),剩下在 CP 和 AP 之间二选一。
2.3 CP 和 AP 的实例
网络分区:节点 A 和 B 失联
CP 选择(牺牲可用性):
ZooKeeper、etcd、HBase、Redis Cluster(写)
→ 少数派分区拒绝服务,宁可不响应也要保证一致
AP 选择(牺牲一致性):
Eureka、Cassandra、DynamoDB、CouchDB
→ 两边都继续响应,事后再合并冲突
2.4 CAP 的常见误解
- "CAP 三选二":不准确,P 必选
- "CP 系统不可用":只是分区时不可用,正常时高可用
- "AP 系统数据不一致":是最终一致,不是永久不一致
- "MySQL 是 CA":单机谈 CAP 没意义
2.5 BASE 理论:AP 的工程化
- Basically Available:基本可用(降级、限流)
- Soft state:软状态(允许中间状态)
- Eventual consistency:最终一致
互联网业界主流:电商、社交、内容都是 AP + BASE,因为可用性 = 钱。
2.6 PACELC:CAP 的扩展
如果有分区(P),在 A 和 C 之间选;否则(E)在延迟(L)和一致性(C)之间选。
更贴近现实:没分区时,强一致也要付出延迟代价(要等多副本确认)。
| 系统 | 模式 |
|---|---|
| ZooKeeper、HBase | PC + EC(始终强一致) |
| Cassandra、DynamoDB | PA + EL(始终低延迟) |
| MongoDB | PA + EC(分区时可用,平时一致) |
三、一致性模型:从强到弱的光谱
强 ←─────────────────────────────────────→ 弱
线性一致性 → 顺序一致性 → 因果一致性 → 最终一致性
(Linearizable) (Sequential) (Causal) (Eventual)
3.1 线性一致性(Linearizability)
最强:所有操作看起来像在某个全局时刻瞬间发生,且符合实际时间顺序。
T1 写 x=1(10:00:00)→ T2 在 10:00:01 读 x,必须读到 1
代价:必须牺牲性能(要全网同步)。ZooKeeper、etcd 提供线性一致。
3.2 顺序一致性
所有进程看到相同的操作顺序,但不要求和真实时间一致。
T1: 10:00 写 x=1
T2: 10:01 写 y=2
所有节点要么先看到 x=1 再 y=2,要么相反,但顺序统一
3.3 因果一致性
有因果关系的操作顺序一致;并发操作不要求顺序。
A 发了帖子 → B 评论了 A 的帖子
所有节点必须先看到帖子,再看到评论(因果)
但 C 同时发的另一个帖子和这俩没关系
3.4 最终一致性
只要不再有写入,最终所有副本会一致。中间过程可以混乱。DNS、邮件系统、电商商品详情都是这种。
3.5 工程取舍
| 业务 | 一致性级别 |
|---|---|
| 银行转账、库存扣减 | 线性一致 |
| 订单状态、支付 | 因果或线性 |
| 商品详情、用户资料 | 最终一致 |
| 点赞数、阅读量 | 最终一致(可丢失) |
四、共识算法:怎么让一群机器达成一致?
4.1 问题定义
N 个节点中,可能有节点宕机或网络抖动,怎么让大家就某个值达成一致?
4.2 Paxos(1989,Lamport)
理论之祖。难懂出名(Lamport 自己用希腊议会比喻才讲清楚)。
核心:
- Proposer:提案者
- Acceptor:接受者
- Learner:学习者
- 两阶段:Prepare → Accept
实际很少直接实现,但 Chubby、Spanner、Zab、Raft 都源自 Paxos 思想。
4.3 Raft(2014,斯坦福)⭐
为可理解性设计,是工程界的事实标准(etcd、Consul、TiKV、CockroachDB 都用 Raft)。
三种角色
┌──→ Leader(唯一):处理所有写
│ ↓ 复制日志
选举 │ Follower:复制 Leader 日志
│ ↑ 选举超时变 Candidate
└─── Candidate:拉票中
核心机制
1. Leader 选举
- 每个节点有 election timeout(150-300ms 随机)
- 超时未收到 Leader 心跳 → 变 Candidate → 给自己投票 → 拉票
- 获过半票 → 成为 Leader
- 任期(Term)单调递增,避免脑裂
2. 日志复制
客户端 → Leader 写日志(uncommitted)
→ 同步给 Follower
→ 过半 Follower ACK
→ Leader 提交(committed)
→ 返回客户端成功
→ 通知 Follower 也提交
3. 安全性约束
- 只有日志最新的节点才能当 Leader(Election Restriction)
- Leader 只能提交当前任期的日志(避免回滚已提交日志)
为什么需要奇数节点?
| 节点数 | 容错(多数派) |
|---|---|
| 3 | 1(多数派 = 2) |
| 4 | 1(多数派 = 3,4 比 3 没收益) |
| 5 | 2 |
| 7 | 3 |
3 节点容忍 1 故障,5 节点容忍 2 故障。增加节点提升容错但拖慢性能(要等更多 ACK)。生产环境通常 3 或 5。
4.4 ZAB(ZooKeeper Atomic Broadcast)
ZooKeeper 用的协议,思想接近 Raft,但出现更早。差异:
- 更强调原子广播
- Leader 选举后有"恢复阶段"同步数据
4.5 Multi-Paxos / Fast Paxos / EPaxos
Paxos 的各种变体,进一步减少消息往返。EPaxos 无 Leader,多点写入,理论很美但工程实现极少。
4.6 拜占庭容错(BFT)
上面这些算法只解决节点宕机或网络问题,不解决节点撒谎(恶意节点)。
拜占庭问题:节点可能发送虚假消息。需要 BFT 算法(如 PBFT、HotStuff),代价高(节点数 ≥ 3f+1,f 是恶意节点数)。
应用:区块链。普通分布式系统假设节点是诚实的,不需要 BFT。
五、分布式事务:跨服务、跨库的 ACID
5.1 问题场景
下单 = 扣库存(库存服务) + 减余额(支付服务) + 创建订单(订单服务)
要么都成功,要么都失败
单库事务靠数据库,跨库就需要分布式事务方案。
5.2 2PC(两阶段提交)
流程
阶段 1:Prepare
协调者 → 所有参与者:你能提交吗?
参与者:执行事务但不提交,返回 yes/no
阶段 2:Commit
全 yes → 协调者 → 所有参与者:提交
有 no → 协调者 → 所有参与者:回滚
致命缺陷
- 同步阻塞:参与者锁资源等协调者命令,期间不能服务
- 协调者单点:协调者宕机 → 参与者一直等
- 数据不一致:阶段 2 部分参与者收到 commit,部分没收到
- 性能极差:两次网络往返 + 锁
现状:仅 XA 协议在传统数据库(MySQL XA、Oracle XA)中保留,互联网业界基本不用。
5.3 3PC(三阶段提交)
引入 CanCommit → PreCommit → DoCommit 三阶段,加入超时机制。理论上改进 2PC 阻塞问题,实际仍有不一致风险,工程几乎不用。
5.4 TCC(Try-Confirm-Cancel)⭐ 互联网常用
业务侵入式的柔性事务。
Try:预留资源(如冻结库存)
Confirm:确认提交(实际扣减)
Cancel:失败回滚(解冻)
实例:转账 100 元
# Try 阶段
account_a.freeze(100) # A 账户冻结 100
account_b.freeze_in(100) # B 账户预增 100
# 全部 Try 成功 → Confirm
account_a.commit() # A 真正扣 100
account_b.commit() # B 真正加 100
# 任一 Try 失败 → Cancel
account_a.unfreeze(100) # A 解冻
account_b.cancel_in(100) # B 取消预增
三大难题
- 空回滚:Try 没执行,Cancel 先到(网络乱序)→ 必须保证幂等且能识别"无 Try 的 Cancel"
- 悬挂:Cancel 先到,Try 后到 → 资源被永久占用
- 幂等:网络重试 → 同一个操作可能调多次
业界框架:Seata(TCC 模式)、ByteTCC、Hmily
5.5 Saga 模式 ⭐ 长事务首选
把长事务拆成一系列本地事务 + 补偿动作。
T1 → T2 → T3 → T4
失败时反向补偿:C3 → C2 → C1
实例:旅游下单
预订机票 → 预订酒店 → 预订租车 → 支付
↓失败时
取消机票 ← 取消酒店 ← 取消租车
两种实现
- 编排式(Choreography):每个服务监听消息,自己决定下一步。简单但难追踪
- 协调式(Orchestration):中心化协调器调度。清晰但有单点
适用场景
- 跨多个服务的长流程(订单、退款、流程审批)
- 不要求强一致,能接受中间状态
- 业界最主流的分布式事务方案
5.6 本地消息表 / 事务消息
核心思想:把"发消息"和"业务操作"放进同一个本地事务。
BEGIN;
UPDATE inventory SET stock = stock - 1;
INSERT INTO outbox (event) VALUES ('stock_deducted');
COMMIT;
-- 后台任务扫 outbox,发到 MQ,确认后删除
优点:简单、可靠、不依赖外部组件 缺点:消息有延迟
RocketMQ 的事务消息是该模式的产品化封装。
5.7 最终一致性 + 对账
最朴素的方案:
- 业务操作记日志
- 后台定时对账(小时级、天级)
- 不一致就修复
适合低频、可容忍延迟的场景(如月底结算)。
5.8 选型决策
| 方案 | 一致性 | 性能 | 复杂度 | 适用 |
|---|---|---|---|---|
| 2PC/XA | 强 | 极差 | 中 | 银行、强一致老系统 |
| TCC | 强 | 中 | 高 | 金融、支付 |
| Saga | 最终 | 好 | 中 | 长流程、跨服务 |
| 本地消息表 | 最终 | 好 | 低 | 通用 |
| 事务消息 | 最终 | 好 | 中 | RocketMQ 生态 |
| 对账 | 最终 | 极好 | 低 | 低频对账 |
互联网业界事实标准:Saga + 本地消息表 + 对账兜底。
六、其他基石概念速览
6.1 幂等性(Idempotence)
多次调用和一次调用效果相同。
分布式系统的生存技能:网络抖动、重试、消息重复都靠幂等扛。
实现手段:
- 唯一 ID(订单号、消息 ID)+ 数据库唯一索引
- 状态机(已支付的订单不能再支付)
- 乐观锁(version 字段)
- Token 机制(一次性令牌)
6.2 分布式 ID
需求:全局唯一、趋势递增、高性能、信息安全。
| 方案 | 优点 | 缺点 |
|---|---|---|
| UUID | 简单 | 无序、太长 |
| DB 自增 | 有序 | 性能差、单点 |
| 号段模式 | 高性能 | 不连续 |
| 雪花算法 (Snowflake) | 有序、高性能 | 时钟回拨问题 |
| Redis incr | 简单高性能 | 依赖 Redis |
| Leaf(美团)/ TinyID | 工业级 | 部署复杂 |
雪花算法
0 | 41 位时间戳 | 10 位机器 ID | 12 位序列号
= 69 年 = 1024 台 = 单机 4096/ms
时钟回拨解决:等待 / 用上次时间 + 1 / 抛异常切机器
6.3 一致性哈希
普通哈希(key % N):节点变化时全部数据要迁移。
一致性哈希:
- 把节点和 key 都哈希到 0~2^32 的环上
- key 顺时针找到的第一个节点 = 归属节点
- 加节点:只影响相邻一段
- 虚拟节点:避免数据倾斜
应用:Redis Cluster(用槽位变种)、Cassandra、CDN、负载均衡
6.4 Gossip 协议
去中心化的最终一致性传播协议(像八卦一样扩散)。
节点定期随机选几个邻居,互换状态
经过 O(log N) 轮,全网收敛
应用:Cassandra 节点发现、Redis Cluster 节点状态、Consul、区块链
6.5 向量时钟(Vector Clock)
解决"分布式系统中事件的因果关系"。每个节点维护一个版本向量 [A:1, B:3, C:2],更新时本地版本 +1,合并时取最大值。Dynamo、Cassandra 用来检测并发写入冲突。
6.6 脑裂(Split Brain)
网络分区导致集群分成两半,各自选了 Leader。两个 Leader 同时接受写 → 数据冲突。
解决:
- 多数派原则(Quorum):少于过半的分区拒绝服务(Raft、ZK)
- fencing:旧 Leader 被新 Leader fence(如让旧 Leader 写共享存储失败)
- 优先级:预设主备优先级
七、CAP × 一致性 × 共识 × 事务 全景图
现实约束:
┌──────────────────────────────┐
│ 网络分区不可避免(P 必选) │
└──────────────┬───────────────┘
│
┌──────┴──────┐
│ │
选 CP 选 AP
(etcd/ZK) (Cassandra)
│ │
┌──────▼──────┐ ┌───▼──────────┐
│ 共识协议 │ │ Gossip + 向量时钟 │
│ Raft/Paxos │ │ 最终一致 │
└──────┬──────┘ └──────┬──────────┘
│ │
┌──────▼──────┐ ┌─────▼──────┐
│ 强一致存储 │ │ 高可用存储 │
│ 选主、配置 │ │ 海量数据 │
└─────────────┘ └────────────┘
跨服务业务(事务层):
┌──────────────────────────────────────┐
│ Saga / TCC / 本地消息表 / 事务消息 │
│ → 牺牲强一致换可用性,业务层补一致 │
└──────────────────────────────────────┘
八、面试高频题速记
- Q:CAP 三选二对吗? A:不准确,P 必选,实际是 CP/AP 二选一
- Q:CAP 和 BASE 关系? A:BASE 是 AP 的工程化(基本可用 + 软状态 + 最终一致)
- Q:Raft 怎么选主? A:随机超时变 Candidate,拉票获过半成 Leader,Term 单调递增防脑裂
- Q:为什么是奇数节点? A:偶数容错没收益(4 和 3 一样只容忍 1 故障)
- Q:脑裂怎么解决? A:多数派原则,少数派分区不工作
- Q:2PC 为什么不用? A:同步阻塞、协调者单点、性能差、不彻底解决一致性
- Q:TCC 三大坑? A:空回滚、悬挂、幂等
- Q:Saga 和 TCC 区别? A:Saga 用补偿(事后回滚),TCC 用预留资源(事前冻结);Saga 适合长流程,TCC 适合短强一致
- Q:本地消息表怎么保证一致? A:业务和消息记录在同一本地事务,后台扫描发送
- Q:雪花算法时钟回拨怎么处理? A:等待回到原时间 / 用最后时间 +1 / 切机器 ID
- Q:一致性哈希解决什么? A:节点变化时大部分数据不需要迁移
- Q:Paxos 和 Raft 区别? A:等价但 Raft 更易懂(强 Leader、明确分阶段)
- Q:拜占庭和非拜占庭区别? A:是否假设节点诚实,区块链需要 BFT