分布式共识算法:Raft与Paxos详解
// 目录 · contents
前言
在分布式系统中,多个节点需要就某个值达成一致,这就是共识问题。共识算法是分布式系统的基石,无论是数据库的主从复制、配置中心的数据同步,还是分布式锁的实现,都依赖共识算法来保证一致性。本文将深入分析两个最重要的共识算法——Paxos 和 Raft,帮助你理解它们的核心原理和实际应用。
共识问题的本质
什么是共识
共识问题的形式化定义:在一个由 N 个节点组成的分布式系统中,即使部分节点发生故障,所有正常节点最终也要就某个提议值达成一致。
共识算法需要满足三个属性:
- 一致性(Agreement):所有正常节点决定的值相同
- 有效性(Validity):决定的值必须是某个节点提议的值
- 终止性(Termination):所有正常节点最终都能做出决定
FLP 不可能定理
Fischer、Lynch 和 Paterson 在 1985 年证明了:在异步网络模型中,即使只有一个节点可能崩溃,也不存在能保证终止性的确定性共识算法。
这意味着实际的共识算法都需要某种形式的妥协——通常是引入超时机制或随机化来绕过 FLP 限制。
Paxos 算法
基本概念
Paxos 由 Leslie Lamport 在 1990 年提出,是第一个被严格证明正确性的共识算法。它定义了三种角色:
- Proposer(提议者):发起提议
- Acceptor(接受者):参与投票,决定是否接受提议
- Learner(学习者):学习最终被选定的值
graph LR
P1[Proposer 1] --> A1[Acceptor 1]
P1 --> A2[Acceptor 2]
P1 --> A3[Acceptor 3]
P2[Proposer 2] --> A1
P2 --> A2
P2 --> A3
A1 --> L1[Learner 1]
A2 --> L1
A3 --> L1
A1 --> L2[Learner 2]
A2 --> L2
A3 --> L2
Basic Paxos:两阶段协议
Phase 1: Prepare
Proposer 选择一个提案编号 n,向多数派 Acceptor 发送 Prepare(n) 请求。
Phase 2: Accept
如果 Proposer 收到多数派的 Promise 响应,它就发送 Accept(n, v) 请求。
sequenceDiagram
participant P as Proposer
participant A1 as Acceptor 1
participant A2 as Acceptor 2
participant A3 as Acceptor 3
Note over P: Phase 1: Prepare
P->>A1: Prepare(n=1)
P->>A2: Prepare(n=1)
P->>A3: Prepare(n=1)
A1-->>P: Promise(n=1, 无已接受值)
A2-->>P: Promise(n=1, 无已接受值)
A3-->>P: Promise(n=1, 无已接受值)
Note over P: 收到多数派Promise<br/>Phase 2: Accept
P->>A1: Accept(n=1, v="X")
P->>A2: Accept(n=1, v="X")
P->>A3: Accept(n=1, v="X")
A1-->>P: Accepted(n=1, v="X")
A2-->>P: Accepted(n=1, v="X")
A3-->>P: Accepted(n=1, v="X")
Note over P: 值 "X" 被选定
竞争场景
当两个 Proposer 同时发起提议时,可能出现活锁(Livelock)。
sequenceDiagram
participant P1 as Proposer 1
participant A1 as Acceptor 1
participant A2 as Acceptor 2
participant A3 as Acceptor 3
participant P2 as Proposer 2
P1->>A1: Prepare(n=1)
P1->>A2: Prepare(n=1)
A1-->>P1: Promise(n=1)
A2-->>P1: Promise(n=1)
Note over P2: P2 使用更高编号
P2->>A2: Prepare(n=2)
P2->>A3: Prepare(n=2)
A2-->>P2: Promise(n=2)
A3-->>P2: Promise(n=2)
P1->>A1: Accept(n=1, v="X")
P1->>A2: Accept(n=1, v="X")
A2-->>P1: Reject(已承诺n=2)
Note over P1: 失败,使用更高编号n=3重试
Note over P1,P2: 可能无限循环...活锁!
Multi-Paxos
Basic Paxos 每决定一个值需要两轮通信,效率太低。Multi-Paxos 通过选举一个稳定的 Leader 来优化:
1 | |
Raft 算法
设计理念
Raft 由 Diego Ongaro 和 John Ousterhout 在 2014 年提出,其核心目标是可理解性。Raft 将共识问题分解为三个相对独立的子问题:
- Leader Election(领导者选举)
- Log Replication(日志复制)
- Safety(安全性保证)
节点状态
Raft 中每个节点有三种状态:
stateDiagram-v2
[*] --> Follower
Follower --> Candidate: 选举超时<br/>未收到心跳
Candidate --> Leader: 获得多数票
Candidate --> Follower: 发现更高任期的Leader
Candidate --> Candidate: 选举超时<br/>重新选举
Leader --> Follower: 发现更高任期
Leader Election
sequenceDiagram
participant F1 as Node A<br/>(Follower)
participant C as Node B<br/>(Candidate)
participant F2 as Node C<br/>(Follower)
participant F3 as Node D<br/>(Follower)
participant F4 as Node E<br/>(Follower)
Note over C: 选举超时,转为Candidate<br/>term: 1 → 2
C->>C: 投票给自己
C->>F1: RequestVote(term=2)
C->>F2: RequestVote(term=2)
C->>F3: RequestVote(term=2)
C->>F4: RequestVote(term=2)
F1-->>C: VoteGranted(term=2)
F2-->>C: VoteGranted(term=2)
F3-->>C: VoteGranted(term=2)
Note over C: 获得4票(含自己)<br/>成为Leader
loop 心跳
C->>F1: AppendEntries(heartbeat)
C->>F2: AppendEntries(heartbeat)
C->>F3: AppendEntries(heartbeat)
C->>F4: AppendEntries(heartbeat)
end
选举超时的随机化是 Raft 避免分票(split vote)的关键机制。每个 Follower 的选举超时时间在 150ms-300ms 之间随机选择。
1 | |
Log Replication
Leader 收到客户端请求后,将命令追加到本地日志,然后并行复制给所有 Follower。当多数派确认后,该日志条目被提交(committed)。
sequenceDiagram
participant Client
participant Leader
participant F1 as Follower 1
participant F2 as Follower 2
participant F3 as Follower 3
participant F4 as Follower 4
Client->>Leader: SET x=5
Note over Leader: 追加到本地日志<br/>index=3, term=2
par 并行复制
Leader->>F1: AppendEntries(index=3, SET x=5)
Leader->>F2: AppendEntries(index=3, SET x=5)
Leader->>F3: AppendEntries(index=3, SET x=5)
Leader->>F4: AppendEntries(index=3, SET x=5)
end
F1-->>Leader: Success
F2-->>Leader: Success
F3-->>Leader: Success
Note over Leader: 3个Follower确认<br/>加上自己共4个<br/>多数派确认,提交!
Leader->>Leader: Apply to State Machine
Leader-->>Client: OK
Note over Leader: 下次心跳通知Follower提交
Leader->>F1: AppendEntries(commitIndex=3)
Leader->>F2: AppendEntries(commitIndex=3)
Leader->>F3: AppendEntries(commitIndex=3)
Leader->>F4: AppendEntries(commitIndex=3)
Safety 保证
Raft 的安全性核心保证是 Leader Completeness Property:如果某个日志条目在给定任期内被提交,那么更高任期的 Leader 一定包含该条目。
这通过 投票限制 来实现:Candidate 在 RequestVote 中携带自己最后一条日志的信息,Follower 只给日志至少和自己一样新的 Candidate 投票。
1 | |
Paxos vs Raft 对比
| 维度 | Paxos | Raft |
|---|---|---|
| 提出时间 | 1990年 | 2014年 |
| 可理解性 | 非常难理解 | 专为可理解性设计 |
| Leader | Multi-Paxos 中有 | 强 Leader |
| 日志 | 允许空洞(holes) | 连续,无空洞 |
| 成员变更 | 未明确规定 | Joint Consensus |
| 正确性证明 | 严格数学证明 | TLA+ 形式化验证 |
| 工程实现 | 各实现差异大 | 实现较为统一 |
实际应用
etcd(使用 Raft)
etcd 是 Kubernetes 的底层存储,使用 Raft 实现数据一致性。
1 | |
1 | |
ZooKeeper(使用 ZAB)
ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议,它是 Paxos 的变种,针对主备模式进行了优化。
sequenceDiagram
participant Client
participant Leader as ZK Leader
participant F1 as ZK Follower 1
participant F2 as ZK Follower 2
Client->>Leader: create /config/db_url
Note over Leader: 生成 zxid<br/>epoch.counter
Leader->>F1: Proposal(zxid, data)
Leader->>F2: Proposal(zxid, data)
F1-->>Leader: ACK
F2-->>Leader: ACK
Note over Leader: 多数派ACK,提交
Leader->>F1: Commit(zxid)
Leader->>F2: Commit(zxid)
Leader-->>Client: OK
1 | |
工程实践建议
集群规模
共识算法要求多数派确认,因此集群节点数推荐为奇数(3、5、7):
- 3 节点:容忍 1 个故障,适合大多数场景
- 5 节点:容忍 2 个故障,适合高可用要求的生产环境
- 7 节点:容忍 3 个故障,极少使用,写入延迟增大
性能优化
graph TB
subgraph 优化手段
A[Batch 批量提交] --> D[减少网络往返]
B[Pipeline 流水线] --> D
C[Learner 只读副本] --> E[分担读压力]
F[PreVote 预投票] --> G[减少不必要选举]
H[快照压缩] --> I[减少日志大小]
end
- Batch:将多个客户端请求合并为一次 Raft 日志提交
- Pipeline:Leader 不等待上一个 AppendEntries 的响应就发送下一个
- Read Index / Lease Read:优化只读请求,避免走完整的 Raft 流程
- PreVote:在正式发起选举前先预投票,避免网络分区恢复后的无谓选举
总结
共识算法是分布式系统的核心基石。Paxos 奠定了理论基础但工程实现困难,Raft 通过分解问题和强 Leader 模型让共识算法变得可理解和可实现。在实际选型中,Raft 已经成为事实标准,被 etcd、Consul、TiKV、CockroachDB 等系统广泛采用。理解这些算法的原理,对于设计和运维分布式系统至关重要。