CAP 定理
2000 年,Eric Brewer 提出 CAP 定理:分布式系统在网络分区发生时,只能在一致性(C)和可用性(A)中二选一。
CAP 三角:
一致性(Consistency)
●
/ \
/ \
/ \
/ 无法 \
/ 同时达到 \
/ \
●───────────────●
可用性(Availability) 分区容忍(Partition Tolerance)
CAP 定理核心:网络分区(P)在分布式系统中不可避免
因此选择是:P + C(牺牲A)或 P + A(牺牲C)
CA 系统:传统单机数据库(MySQL 单节点),不考虑网络分区
实际的分布式系统必须具备 P,所以真正的选择是 CP 或 AP
CP 系统(优先一致性):
ZooKeeper、etcd、HBase
网络分区时,拒绝服务(返回错误)而不返回过时数据
适用:金融系统、配置中心、选举服务
AP 系统(优先可用性):
Cassandra、DynamoDB、CouchDB
网络分区时,继续服务但可能读到旧数据
适用:购物车、点赞数、DNS、内容推送
⚠ CAP 误解澄清
CAP 中的 C 是线性一致性(所有节点看到完全一致的最新状态),这是最强的一致性级别。实际系统中有更多选择,不是非此即彼。而且 CAP 只在网络分区发生的那一刻成立——平时 CA 都可以兼顾。
BASE 理论
ACID 是关系数据库的事务保证,BASE 是互联网大规模分布式系统的务实选择:
BA — Basically Available
基本可用
基本可用
允许系统在故障时降级,牺牲部分功能保核心可用。如:淘宝双11降级评论功能,只保证下单/支付。
S — Soft State
软状态
软状态
允许系统中存在中间状态,数据在副本间可以不同步(存在复制延迟),这是允许的。
E — Eventually Consistent
最终一致
最终一致
系统在没有新写入的情况下,经过一段时间后,所有副本的数据最终会收敛到一致状态。
一致性级别:从强到弱
一致性强度(从强到弱):
┌─────────────────────────────────────────────────────────┐
│ 线性一致性(Linearizability) │
│ 最强。所有操作看起来是瞬时发生的,全局有序 │
│ 代价:高延迟(需要协调所有节点) │
│ 应用:etcd、ZooKeeper、Google Spanner │
├─────────────────────────────────────────────────────────┤
│ 顺序一致性(Sequential Consistency) │
│ 所有节点看到的操作顺序相同,但不要求实时 │
│ 早于线性一致但晚于因果一致 │
├─────────────────────────────────────────────────────────┤
│ 因果一致性(Causal Consistency) │
│ 有因果关系的操作保证顺序,无关操作无需排序 │
│ "你看到我的回复,必须先看到我回复的那条帖子" │
├─────────────────────────────────────────────────────────┤
│ 读自己写(Read-Your-Writes) │
│ 用户总能读到自己刚写的数据(不保证读到别人的最新写) │
├─────────────────────────────────────────────────────────┤
│ 最终一致性(Eventual Consistency) │
│ 最弱。只保证最终会一致,中间可能不一致 │
│ 应用:DNS、Cassandra、Amazon S3 │
└─────────────────────────────────────────────────────────┘
Paxos 与 Raft:共识算法
共识算法解决的问题:多个节点如何就同一个值达成一致意见,即使部分节点宕机或消息延迟。
Paxos 算法(Leslie Lamport,1989)
Paxos 两阶段流程(简化):
角色:Proposer(提议者)、Acceptor(接受者)、Learner(学习者)
阶段1:Prepare
┌─────────────────────────────────────────┐
│ Proposer 广播 Prepare(n) 给所有 Acceptor │
│ n = 提案编号(全局递增) │
│ │
│ Acceptor 收到 Prepare(n): │
│ 若 n > 之前见过的最大提案号: │
│ 承诺不接受编号 < n 的提案 │
│ 返回之前接受过的值(如果有) │
└─────────────────────────────────────────┘
阶段2:Accept
┌─────────────────────────────────────────┐
│ Proposer 收到多数 Acceptor 的 Promise │
│ 广播 Accept(n, value) 给多数 Acceptor │
│ │
│ Acceptor 收到 Accept(n, v): │
│ 若未承诺拒绝此提案:接受并回复 │
│ │
│ Proposer 收到多数 Accept → 提案被批准! │
└─────────────────────────────────────────┘
核心思想:需要「多数派(Quorum)」同意,容忍少数节点失败
5节点集群:允许2个节点失败,仍可运作(3/5 > 50%)
Raft 算法(Diego Ongaro,2014)
Raft 是 Paxos 的「可理解版」,把共识问题分解为三个相对独立的子问题:
Raft 关键角色:
Leader(领导者):处理所有写请求,向 Follower 复制日志
Follower(追随者):被动接收 Leader 的日志复制
Candidate(候选人):选举过程中的临时角色
─────────────────────────────────────────────
Leader 选举流程:
① 正常状态:Leader 定期发送心跳给所有 Follower
② Follower 超时(未收到心跳):
转变为 Candidate,Term +1
给自己投票,广播 RequestVote 给其他节点
③ 收到多数票 → 成为新 Leader
开始向所有 Follower 发送心跳,宣告主权
─────────────────────────────────────────────
日志复制流程:
Client ──▶ Leader
Leader:
1. 将命令追加到本地日志
2. 并行发送 AppendEntries 给所有 Follower
3. 收到多数 Follower 确认 → 提交(Commit)
4. 返回客户端成功
5. 通知所有 Follower 提交
即使 Leader 在步骤4后宕机:
多数 Follower 已有此日志
新 Leader 会重新复制给剩余节点
数据不丢失!
向量时钟(Vector Clock)
分布式系统中,节点各自有本地时钟,无法用时间戳判断事件的先后顺序。向量时钟通过逻辑计数器追踪因果关系。
向量时钟示例(3个节点:A、B、C):
每个节点维护一个向量 [A_count, B_count, C_count]
A 初始:[1,0,0] B 初始:[0,1,0] C 初始:[0,0,1]
① A 发送消息给 B,携带时钟 [1,0,0]:
B 收到后:取 max([1,0,0], [0,1,0]) = [1,1,0],B计数+1 = [1,2,0]
② B 发送消息给 C,携带时钟 [1,2,0]:
C 收到后:取 max([1,2,0], [0,0,1]) = [1,2,1],C计数+1 = [1,2,2]
比较两个事件 e1=[1,2,0] 和 e2=[1,2,2]:
e1 中每个值 ≤ e2 的对应值 → e1 happened-before e2(因果关系)
比较 e1=[2,0,0] 和 e2=[0,2,0]:
既不是 e1≤e2,也不是 e2≤e1 → 并发事件(concurrent),无法判断顺序
应用:Amazon DynamoDB 使用向量时钟检测并发冲突
分布式锁
多个服务实例需要互斥访问共享资源时,本地锁无效,需要分布式锁。
基于 Redis 的分布式锁(Redlock)
Redis 简单分布式锁(单节点):
SET lock_key unique_token NX EX 30
↑ ↑ ↑ ↑ ↑
命令 Key 唯一值(防误删) NX=不存在才设置 EX=超时秒数
获取锁:
result = redis.SET("resource:lock", uuid, NX=True, EX=30)
if result == "OK": # 获取成功
try:
do_work()
finally:
# 原子性释放:只删除自己的锁
redis.eval("""
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
""", keys=["resource:lock"], args=[uuid])
─────────────────────────────────────────────
Redlock 算法(多节点,更安全):
5个独立的 Redis 节点
向所有5个节点尝试获取锁
收到3个以上成功(多数派)→ 加锁成功
整个过程耗时 < 锁有效期 / 2
释放时向所有节点发送解锁命令
基于 ZooKeeper 的分布式锁
ZooKeeper 分布式锁(更强保证):
原理:
1. 所有竞争者在 /locks/resource/ 下创建临时有序节点
如:/locks/resource/lock-0000001
/locks/resource/lock-0000002
/locks/resource/lock-0000003
2. 获取 /locks/resource/ 下所有子节点,排序
3. 如果自己是序号最小的节点 → 获得锁!
4. 否则 → Watch 比自己小的前一个节点
5. 当前一个节点消失时 → 重新检查是否轮到自己
优点:公平锁(先来先得),不会有惊群效应
ZK 节点挂掉(临时节点),锁自动释放(防死锁)
缺点:ZK 操作比 Redis 慢(需要 Paxos 共识)
▶ 面试要点
- CAP 中:P(分区容忍)在分布式系统中是必须的,真正的选择是 CP 还是 AP。
- Raft vs Paxos:两者解决同一问题,Raft 更易理解和实现(etcd、TiKV、CockroachDB 都用 Raft)。
- 分布式锁的锁超时设置是艺术:太短会在业务未完成时锁过期,太长会在持锁者宕机后长时间无法重新获锁。必须有看门狗(Watchdog)机制自动续期。