分布式理论
概念
分布式系统是指多台计算机通过网络协同工作,对外表现为一个整体的系统。核心挑战在于:网络不可靠、节点可能宕机、时钟无法完全同步。
关键术语
| 术语 | 含义 |
|---|---|
| 节点(Node) | 分布式系统中的一台独立机器或进程 |
| 分区(Partition) | 网络故障导致节点之间无法通信 |
| 副本(Replica) | 同一份数据的多个拷贝,用于容错和提升读性能 |
| 共识(Consensus) | 多个节点就某个值达成一致的过程 |
| 幂等性(Idempotency) | 同一操作执行多次与执行一次效果相同 |
核心原理
1. CAP 定理
CAP 由 Eric Brewer 于 2000 年提出,指出分布式系统无法同时满足以下三个特性,最多只能满足其中两个:
- C(Consistency,一致性):所有节点在同一时刻看到的数据完全一致(强一致性)
- A(Availability,可用性):每个请求都能收到响应(不保证数据是最新的)
- P(Partition Tolerance,分区容错):网络发生分区时,系统仍能继续运行
为什么 P 必须选?
在真实的分布式环境中,网络分区是客观存在且无法完全避免的——网线故障、交换机宕机、跨机房延迟都可能造成分区。放弃 P 意味着系统在网络抖动时直接停止服务,这在生产环境中不可接受。因此 P 是必选项,真正的选择只在 CP vs AP。
CP vs AP 系统举例
| 类型 | 代表系统 | 行为 |
|---|---|---|
| CP | ZooKeeper、etcd、HBase | 网络分区时拒绝写请求,保证数据一致 |
| AP | Eureka、Cassandra、CouchDB | 网络分区时仍接受读写,允许数据短暂不一致 |
ZooKeeper 选择 CP:Leader 选举期间集群不可用,但数据强一致,适合配置中心、分布式锁。
Eureka 选择 AP:节点故障时不下线其他节点的注册信息,优先保证服务发现可用,适合微服务注册中心。
2. BASE 理论
BASE 是对 CAP 中 AP 方向的工程实践总结,由 eBay 架构师 Dan Pritchett 提出:
- Basically Available(基本可用):允许响应时间变长或部分功能降级,核心功能仍然可用
- Soft State(软状态):系统中的数据可以存在中间状态,不要求时刻一致
- Eventually Consistent(最终一致性):数据在一段时间后会最终达到一致状态
BASE vs ACID 对比
| 维度 | ACID | BASE |
|---|---|---|
| 适用场景 | 单机/强一致性关系数据库 | 分布式/高可用系统 |
| 一致性要求 | 强一致性 | 最终一致性 |
| 可用性 | 发生冲突时可能阻塞 | 优先保证可用性 |
| 代表系统 | MySQL、PostgreSQL | DynamoDB、Cassandra |
| 核心思想 | 事务要么全做要么全不做 | 接受短暂不一致,换取高可用 |
3. 一致性模型
从强到弱,常见的一致性模型如下:
| 模型 | 定义 | 代价 | 典型应用 |
|---|---|---|---|
| 强一致性 | 写完之后所有节点立即可见最新数据 | 延迟高,可用性低 | ZooKeeper、etcd |
| 顺序一致性 | 所有节点看到的操作顺序相同,但不要求实时 | 中等 | 早期分布式数据库 |
| 因果一致性 | 有因果关系的操作按顺序可见,无关操作可乱序 | 较低 | MongoDB(因果会话) |
| 最终一致性 | 在没有新更新的前提下,数据最终会收敛一致 | 最低 | DNS、Cassandra、S3 |
4. Paxos 算法
Paxos 是经典的分布式共识算法,由 Leslie Lamport 提出。
三种角色
- Proposer(提案者):发起提案,请求集群就某个值达成共识
- Acceptor(接受者):投票接受或拒绝提案
- Learner(学习者):学习最终被接受的值,不参与投票
两阶段流程
阶段一:Prepare(准备阶段)
- Proposer 生成全局唯一编号
n,向多数派 Acceptor 发送Prepare(n)请求 - Acceptor 收到后,若
n大于已见过的最大编号,则承诺不再接受编号小于n的提案,并返回已接受的最大编号提案
阶段二:Accept(接受阶段)
- Proposer 收到多数派响应后,选取编号最大的已接受值(若无则用自己的值),发送
Accept(n, value) - Acceptor 若未承诺更高编号,则接受该提案并通知 Learner
Multi-Paxos
Basic Paxos 每次共识都需要两轮 RPC,开销较大。Multi-Paxos 优化:选出一个稳定的 Leader,后续日志直接跳过 Prepare 阶段,只执行 Accept,大幅减少通信轮次。
5. Raft 算法
Raft 由 Diego Ongaro 和 John Ousterhout 于 2014 年提出,目标是比 Paxos 更易理解,被 etcd、TiKV、CockroachDB 广泛采用。
三种状态
- Follower(跟随者):被动接收 Leader 的日志和心跳
- Candidate(候选者):发起选举时的临时状态
- Leader(领导者):负责处理所有客户端请求,复制日志
Leader 选举过程(ASCII 图)
初始状态:所有节点均为 Follower
Node A [Follower] Node B [Follower] Node C [Follower]
| | |
| 超时未收到心跳 | |
| | |
v | |
Node A [Candidate] | |
| | |
|--- RequestVote(term=1) --> |
|--- RequestVote(term=1) ----------------->
| | |
|<-- VoteGranted ---- |
|<-- VoteGranted ----------------------------
| | |
v 获得多数票 | |
Node A [Leader] | |
| | |
|--- Heartbeat ------> |
|--- Heartbeat -------------------------------->日志复制流程
- 客户端向 Leader 发送写请求
- Leader 将日志条目追加到本地日志,并并行发送
AppendEntries给所有 Follower - 多数派(Quorum)写入成功后,Leader 提交该条目并应用到状态机
- Leader 在下次心跳中通知 Follower 提交
安全性保证
- 选举限制:只有拥有最新日志的节点才能成为 Leader(日志比较:term 更大,或 term 相同但 index 更大)
- 任期(Term):全局单调递增的逻辑时钟,用于检测过时信息
实际应用
| 系统 | 用途 |
|---|---|
| etcd | Kubernetes 集群元数据存储 |
| TiKV | TiDB 的分布式存储层 |
| CockroachDB | 分布式 SQL 数据库 |
| Consul | 服务发现与配置中心 |
6. ZAB 协议
ZAB(ZooKeeper Atomic Broadcast)是 ZooKeeper 专用的一致性协议,与 Raft 思路相近但有所不同。
核心流程
- 崩溃恢复(Leader Election):Leader 宕机后,选出拥有最新 zxid(事务 ID)的节点作为新 Leader
- 消息广播(Broadcast):类似两阶段提交,Leader 向所有 Follower 广播 Proposal,收到多数 ACK 后提交
ZAB vs Raft 对比
| 维度 | ZAB | Raft |
|---|---|---|
| 设计目标 | ZooKeeper 专用 | 通用共识算法 |
| 日志提交 | Follower 可不存完整历史 | 必须有完整日志才能成为 Leader |
| 顺序保证 | 严格有序(FIFO) | 严格有序 |
| 易理解性 | 较复杂 | 更清晰,有完整论文 |
7. 一致性哈希
一致性哈希(Consistent Hashing)解决了传统取模哈希在节点增删时导致大量数据迁移的问题。
哈希环
将哈希空间组织成一个首尾相接的环( 到 ),节点和数据都映射到环上:
0
/ \
300 60
[NodeC] [NodeA]
| |
240 120
[NodeB]
180数据按顺时针方向找到第一个节点进行存储。新增或删除节点时,只影响该节点相邻的数据,其余数据不受影响。
虚拟节点解决数据倾斜
真实部署中,节点数量少时哈希环分布不均,导致数据倾斜(某些节点负载远高于其他节点)。
解决方案:为每个物理节点创建多个虚拟节点(Virtual Node),分散映射到环上:
物理节点 NodeA → 虚拟节点 NodeA#1, NodeA#2, NodeA#3 ... 均匀分布在环上虚拟节点数量越多,数据分布越均匀,通常每个物理节点配置 100~200 个虚拟节点。
实际应用
| 系统 | 用途 |
|---|---|
| Cassandra | 数据分片与副本放置 |
| Amazon DynamoDB | 分区键路由 |
| Nginx(一致性哈希模块) | 负载均衡 |
| Memcached 客户端 | 缓存节点路由 |
8. 分布式时钟
在分布式系统中,物理时钟无法做到完全同步(网络延迟、晶振漂移等原因),因此无法仅靠物理时间判断事件的先后顺序。逻辑时钟和混合时钟的目标是在不依赖绝对物理时间的前提下,准确追踪事件的因果关系。
Lamport Clock(兰伯特时钟)
- 标量逻辑时钟,每个进程维护一个单调递增的计数器
- 规则:本地事件发生时计数器加一;发送消息时附带当前计数器值;接收消息时取
max(本地计数器, 消息计数器) + 1 - 局限:可以判断"先发生(happened-before)"关系,但无法区分并发事件——如果
a → b则C(a) < C(b),但C(a) < C(b)不能推出a → b
Vector Clock(向量时钟)
- 每个进程维护一个长度为 N(进程数)的计数器向量
- 既能判断因果关系,又能检测并发事件(两个事件互相不能比较大小即为并发)
- DynamoDB、Riak 使用向量时钟进行冲突检测
- 缺点:空间复杂度 ,进程数增加时开销显著
HLC(Hybrid Logical Clock,混合逻辑时钟)
- 结合物理时间戳与逻辑计数器:物理时间提供粗粒度排序,逻辑计数器提供细粒度因果追踪
- 与物理时间的偏差有上界,表示紧凑()
- CockroachDB、YugabyteDB 采用 HLC
- 优势:在保持因果追踪能力的同时,与真实时间的偏差可被控制在毫秒级
三种时钟对比
| 时钟类型 | 能否判断因果 | 能否检测并发 | 空间复杂度 | 典型应用 |
|---|---|---|---|---|
| Lamport Clock | 是 | 否 | O(1) | 简单排序场景 |
| Vector Clock | 是 | 是 | O(N) | DynamoDB、Riak |
| HLC | 是 | 部分 | O(1) | CockroachDB |
9. FLP 不可能定理
结论:在异步系统中,即使只有一个进程可能发生故障,也不存在确定性的共识算法能保证在有限时间内终止。
工程含义:所有实际的共识算法(Paxos、Raft)都依赖超时机制和随机化来取得进展。FLP 定理告诉我们异步共识的理论极限,但实践中纯异步系统极为罕见,超时机制让算法在大多数情况下能正常工作。
10. ZooKeeper vs etcd 选型对比
ZooKeeper 和 etcd 都是分布式协调服务,提供强一致性的配置存储、服务发现和分布式锁能力,但在协议、数据模型和生态上存在明显差异。
架构对比
| 维度 | ZooKeeper | etcd |
|---|---|---|
| 语言 | Java | Go |
| 一致性协议 | ZAB | Raft |
| 数据模型 | 树形(类文件系统,ZNode) | 扁平 KV(键值对) |
| 临时节点 | 支持(Ephemeral Node) | 通过 Lease 实现 |
| Watch 机制 | 一次性触发,需重新注册 | 持续 Watch,基于 Revision |
| 事务支持 | Multi 操作 | Mini 事务(If-Then-Else) |
| 性能 | 读多写少场景优秀 | 读写均衡 |
| 运维复杂度 | JVM 调优,相对复杂 | 单二进制,运维简单 |
| 生态 | Hadoop/Kafka/HBase 生态 | Kubernetes/Cloud Native 生态 |
选型建议
- 已有 Hadoop/Kafka 生态 → ZooKeeper
- Kubernetes / 云原生项目 → etcd
- 需要临时节点语义(如分布式锁)→ ZooKeeper 更自然
- 追求运维简单 → etcd
ZooKeeper 分布式锁实现示例
1. 客户端在 /locks/resource 下创建临时顺序节点 /locks/resource/lock-000001
2. 获取 /locks/resource 下所有子节点,排序
3. 如果自己是最小节点 → 获得锁
4. 否则 → Watch 前一个节点(如 lock-000000)
5. 前一个节点删除(释放锁或会话超时)→ 收到通知,重新检查
6. 客户端断开连接 → 临时节点自动删除 → 锁自动释放面试常问 & 怎么答
Q1: CAP 定理是什么?为什么不能同时满足?举例说明 CP 和 AP 系统
答题思路(1 分钟版本)
CAP 定理指出,分布式系统无法同时保证一致性(C)、可用性(A)、分区容错(P)三者,最多满足其二。
为什么不能三者兼得?假设节点 A 和 B 之间发生网络分区,此时向 A 写入数据,B 无法同步。如果要保证 C,就必须拒绝 B 的读请求,牺牲 A;如果要保证 A,B 继续提供服务,但数据可能过期,牺牲 C。两者无法同时满足。
由于网络分区在生产中不可避免,P 必须保留,真正的取舍只在 CP vs AP:
- CP 系统:ZooKeeper——选举期间拒绝写入,保证强一致,适合配置中心、分布式锁。
- AP 系统:Eureka——节点故障时保留旧注册信息,优先可用,适合服务发现。
Q2: Raft 算法的核心流程?Leader 选举怎么做?
答题思路(1 分钟版本)
Raft 将一致性问题分解为三个子问题:Leader 选举、日志复制、安全性。
Leader 选举:节点初始为 Follower,超过选举超时未收到 Leader 心跳后,自增 term 转为 Candidate,向其他节点发 RequestVote 请求。获得多数票后成为 Leader,并立即发心跳阻止新选举。
选举安全性:投票时节点只给"日志不比自己旧"的 Candidate 投票,确保 Leader 一定拥有所有已提交的日志。
日志复制:客户端写请求发给 Leader,Leader 追加日志后并行发给 Follower,多数派写成功后提交,回复客户端。
Q3: 一致性哈希是什么?虚拟节点解决什么问题?
答题思路(1 分钟版本)
一致性哈希将哈希空间构成一个环,节点和数据都映射到环上,数据顺时针找最近节点存储。好处是新增或删除节点时,只有相邻的少量数据需要迁移,其余不受影响——传统取模哈希在节点数变化时几乎所有数据都要重新分配。
虚拟节点解决数据倾斜问题:节点少时环上分布不均,负载集中在少数节点。为每个物理节点创建多个虚拟节点均匀散布在环上,使数据分布更均匀,负载更平衡。Cassandra 和 DynamoDB 都使用了这一机制。
Q4: 分布式时钟有哪些?解决什么问题?
答题思路(1 分钟版本)
分布式系统中物理时钟无法完全同步,需要逻辑时钟来确定事件的先后顺序。
三种主要方案:Lamport Clock 是最简单的标量时钟,能判断因果关系但无法检测并发事件;Vector Clock 为每个进程维护一个计数器向量,既能判断因果又能检测并发,DynamoDB 和 Riak 使用它来做冲突检测,但空间复杂度 O(N);HLC 混合逻辑时钟结合物理时间和逻辑计数器,CockroachDB 使用它,兼顾了紧凑表示和因果追踪。
实际选型:大多数分布式数据库使用 HLC,分布式存储系统使用 Vector Clock,简单场景用 Lamport Clock 足够。
Q5: ZooKeeper 和 etcd 怎么选?
答题思路(1 分钟版本)
两者都是分布式协调服务,核心区别在协议、数据模型和生态。
ZooKeeper 使用 ZAB 协议,树形数据模型,原生支持临时节点和一次性 Watch,是 Hadoop/Kafka 生态的标配。etcd 使用 Raft 协议,扁平 KV 模型,持续 Watch 基于 Revision,是 Kubernetes 的核心组件。
选型建议:大数据生态(Kafka、HBase)选 ZooKeeper;云原生/K8s 生态选 etcd;需要分布式锁且已有 ZK 则继续用 ZK;新项目追求运维简单选 etcd(Go 单二进制,无需 JVM 调优)。
看到什么就先想到这类
| 关键词 / 场景 | 优先想到 |
|---|---|
| 分布式系统设计取舍 | CAP 定理,先判断 CP 还是 AP |
| 高可用 vs 强一致 | BASE 理论,最终一致性 |
| 配置中心 / 分布式锁 | ZooKeeper(CP)、etcd(Raft) |
| 服务注册发现 | Eureka(AP)、Consul(CP) |
| 多节点如何达成共识 | Raft(工程常用)、Paxos(理论基础) |
| etcd / TiKV 内部原理 | Raft 算法 |
| ZooKeeper 内部原理 | ZAB 协议 |
| 缓存 / 存储分片路由 | 一致性哈希 + 虚拟节点 |
| 节点增删数据迁移代价 | 一致性哈希(vs 取模哈希) |
| 数据倾斜 / 热点 | 虚拟节点,增加哈希环分布均匀度 |
| 数据库事务 vs 分布式存储 | ACID vs BASE 对比 |
| 事件排序 / 因果关系 / 并发检测 | 分布式时钟(Lamport/Vector/HLC) |
| K8s 元数据 / 云原生服务发现 | etcd(Raft) |
| 临时节点 / 一次性 Watch | ZooKeeper(ZAB) |
| 异步系统共识的理论极限 | FLP 不可能定理 |