副标题:让分布式系统达成一致的奥秘 🎯
🎬 开场:为什么需要共识算法?
分布式系统的核心难题
场景:3个节点的分布式系统
问题:如何保证这3个节点的数据一致?
┌──────┐ ┌──────┐ ┌──────┐
│节点A │ │节点B │ │节点C │
│data=1│ │data=1│ │data=1│
└──────┘ └──────┘ └──────┘
客户端发起更新:data = 2
理想情况:
┌──────┐ ┌──────┐ ┌──────┐
│节点A │ │节点B │ │节点C │
│data=2│ │data=2│ │data=2│ ✅ 一致
└──────┘ └──────┘ └──────┘
但现实是:
网络分区:
┌──────┐ × ┌──────┐ ┌──────┐
│节点A │ (断网) │节点B │ │节点C │
│data=2│ │data=1│ │data=1│ ❌ 不一致
└──────┘ └──────┘ └──────┘
节点崩溃:
┌──────┐ ┌──────┐ ┌──────┐
│节点A │ │节点B │ │节点C │
│data=2│ │data=2│ │ 💥 │ ❌ 数据丢失
└──────┘ └──────┘ └──────┘
并发写入:
┌──────┐ ┌──────┐ ┌──────┐
│节点A │ │节点B │ │节点C │
│data=2│ │data=3│ │data=4│ ❌ 冲突
└──────┘ └──────┘ └──────┘
共识算法的作用
共识(Consensus):
让分布式系统中的多个节点对某个值达成一致
保证:
1. 一致性:所有节点的值相同
2. 可用性:系统能够响应请求
3. 分区容错:网络分区时仍能工作
应用场景:
├── 分布式数据库:主从复制
├── 配置中心:配置同步
├── 分布式锁:锁的持有者
├── 分布式队列:消息顺序
└── Leader选举:选出主节点
📚 Paxos算法
历史背景
发明者:Leslie Lamport(图灵奖得主)
发布时间:1998年
论文标题:The Part-Time Parliament
(兼职议会)
特点:
✅ 理论完备
✅ 安全性证明严谨
❌ 难以理解
❌ 难以实现
核心角色
Paxos的三种角色:
1. Proposer(提议者)
- 提出提案
- 发起投票
2. Acceptor(接受者)
- 接受提案
- 进行投票
3. Learner(学习者)
- 学习被选定的值
- 不参与投票
实际系统中,一个节点通常扮演多个角色
Basic Paxos流程
两阶段提交:
阶段1:Prepare(准备阶段)
┌─────────┐
│Proposer │ 发送 Prepare(n)
└────┬────┘
│
↓
┌────────────────────────────┐
│ Acceptor A, B, C │
│ 收到提案编号 n │
│ 如果 n > 之前收到的最大编号│
│ 则承诺: │
│ - 不再接受编号 < n 的提案│
│ - 返回已接受的最大提案 │
└────────────────────────────┘
阶段2:Accept(接受阶段)
┌─────────┐
│Proposer │ 发送 Accept(n, value)
└────┬────┘
│
↓
┌────────────────────────────┐
│ Acceptor A, B, C │
│ 如果 n >= 承诺的最小编号 │
│ 则接受该提案 │
│ 否则拒绝 │
└────────────────────────────┘
达成共识:
多数派(Quorum)接受 → 提案通过
示例流程
场景:3个Acceptor(A, B, C),需要2个同意
时刻1:Proposer1 提案
P1 → A, B, C: Prepare(1)
A: 承诺 ✅ (maxN=1)
B: 承诺 ✅ (maxN=1)
C: 承诺 ✅ (maxN=1)
P1 收到多数派承诺 → 进入Accept阶段
时刻2:Proposer1 提交
P1 → A, B, C: Accept(1, "value_X")
A: 接受 ✅ (acceptedN=1, acceptedV="value_X")
B: 接受 ✅ (acceptedN=1, acceptedV="value_X")
C: 接受 ✅ (acceptedN=1, acceptedV="value_X")
P1 收到多数派接受 → 提案通过 ✅
共识结果:value_X
时刻3:并发场景
P2 → A, B, C: Prepare(2)
A: 承诺 ✅ (maxN=2, 返回已接受的(1, "value_X"))
B: 承诺 ✅ (maxN=2, 返回已接受的(1, "value_X"))
C: 延迟/崩溃
P2 发现已有提案被接受,必须使用 value_X
P2 → A, B: Accept(2, "value_X")
A: 接受 ✅
B: 接受 ✅
最终一致:都是 value_X ✅
Multi-Paxos
问题:Basic Paxos每次只能决定一个值
Multi-Paxos:
1. 选举一个Leader
2. Leader独占提案权
3. 跳过Prepare阶段(Leader已获承诺)
4. 直接Accept
优势:
- 减少消息数量
- 提高性能
- 接近实用
流程:
┌────────┐
│ Leader │ 发送 Accept(n, value)
│ 选举 │
└───┬────┘
│
↓
┌─────────────────┐
│ Acceptor A,B,C │
│ 直接接受 │
└─────────────────┘
🎯 Raft算法
设计目标
发明者:Diego Ongaro, John Ousterhout
发布时间:2013年
论文标题:In Search of an Understandable
Consensus Algorithm
(寻找可理解的共识算法)
设计目标:
✅ 易于理解(Understandability)
✅ 易于实现
✅ 等价于Multi-Paxos的安全性
✅ 更好的性能
成果:
- 被广泛应用(etcd, Consul, TiKV等)
- 工业界事实标准
核心角色
Raft的三种状态:
1. Leader(领导者)
- 处理所有客户端请求
- 复制日志到Follower
- 唯一的写入点
2. Follower(跟随者)
- 被动接收Leader的日志
- 投票选举Leader
3. Candidate(候选人)
- 发起选举
- 尝试成为Leader
状态转换:
Follower → (超时) → Candidate → (获得多数票) → Leader
↑ │
└───────────────(发现更高term)──────────────────┘
Raft的三个子问题
1️⃣ Leader选举(Leader Election)
选举触发:
- Follower在选举超时内未收到心跳
- 转变为Candidate并发起选举
选举流程:
┌──────────┐
│Candidate │
└────┬─────┘
│
├─ 增加任期号(term)
├─ 投票给自己
└─ 向所有节点请求投票
│
↓
┌───────────────────────┐
│ 其他节点收到请求 │
│ 检查: │
│ 1. term >= 自己的term │
│ 2. 日志至少和自己一样新│
│ 3. 本term还没投过票 │
│ → 投赞成票 │
└───────────────────────┘
│
↓
┌──────────┐
│Candidate │ 收到多数票
└────┬─────┘
│
└─ 成为Leader ✅
时间线示例:
Term 1: A是Leader
Term 2: A崩溃,B超时,发起选举,成为Leader
Term 3: B和C网络分区,C发起选举,成为Leader
2️⃣ 日志复制(Log Replication)
复制流程:
Step 1: 客户端请求
Client → Leader: 写入 data=X
Step 2: Leader写入日志
Leader:
Log: [..., (term=3, index=5, data=X)]
状态:Uncommitted(未提交)
Step 3: Leader复制到Follower
Leader → Follower A: AppendEntries(...)
Leader → Follower B: AppendEntries(...)
Leader → Follower C: AppendEntries(...)
Step 4: Follower写入日志
Follower A: Log: [..., (term=3, index=5, data=X)] ✅
Follower B: Log: [..., (term=3, index=5, data=X)] ✅
Follower C: 网络延迟...
Step 5: 多数派确认
Leader收到 A, B 的确认 → 多数派达成 ✅
Step 6: Leader提交
Leader:
Log: [..., (term=3, index=5, data=X)]
状态:Committed(已提交)
commitIndex = 5
Step 7: 通知Follower提交
Leader → Follower A, B: commitIndex = 5
Follower A, B: 提交日志 ✅
Step 8: 响应客户端
Leader → Client: 写入成功 ✅
日志结构:
Leader的日志:
Index: 1 2 3 4 5 6
Term: 1 1 2 2 3 3
Data: a b c d e f
─────────────────────
已提交(commitIndex=4)
Follower的日志(正常):
Index: 1 2 3 4 5
Term: 1 1 2 2 3
Data: a b c d e
─────────────────
已提交(commitIndex=4)
Follower的日志(落后):
Index: 1 2 3
Term: 1 1 2
Data: a b c
─────────
已提交(commitIndex=3)
Follower的日志(冲突):
Index: 1 2 3 4 5
Term: 1 1 2 2 2 ← term冲突
Data: a b c d x
─────────────────
Leader会覆盖冲突的日志
3️⃣ 安全性(Safety)
安全性保证:
1. 选举安全性(Election Safety)
一个term最多一个Leader
2. Leader完整性(Leader Completeness)
如果一个日志条目在某个term被提交,
那么这个条目必然出现在更高term的Leader的日志中
3. 状态机安全性(State Machine Safety)
如果某个节点已应用了某个日志条目到状态机,
则其他节点不会在相同index应用不同的日志
关键机制:
- 投票时检查日志新旧程度
- 只有拥有最新已提交日志的节点才能当选Leader
🆚 Raft vs Paxos 对比
可理解性对比
Paxos:
❌ 难以理解
- 角色复杂(Proposer, Acceptor, Learner)
- 两阶段协议晦涩
- 缺乏清晰的实现指导
❌ 难以实现
- 论文偏理论
- 实现细节需要自己补充
- 容易出错
Raft:
✅ 易于理解
- 角色清晰(Leader, Follower, Candidate)
- 流程直观(选举 → 日志复制)
- 问题分解(Leader选举、日志复制、安全性)
✅ 易于实现
- 论文详细
- 提供实现指导
- 有参考实现
性能对比
消息复杂度:
Paxos(Basic):
- Prepare阶段:2n
- Accept阶段:2n
- 总计:4n
Multi-Paxos:
- Leader选举后:2n
- 性能接近Raft
Raft:
- 正常情况:2n
- Leader选举:O(n²)(但很少发生)
结论:性能相当 ≈
应用场景对比
| 维度 | Paxos | Raft |
|---|---|---|
| 理论完备性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 易理解性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 易实现性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 工业应用 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
🛠️ 实际应用
Raft应用
1. etcd(Kubernetes配置中心)
- Leader处理所有写请求
- Follower转发给Leader
- 保证配置一致性
2. Consul(服务发现)
- 服务注册表一致性
- Leader选举
- KV存储
3. TiKV(分布式KV)
- 数据分片
- 每个分片一个Raft组
- 保证数据一致性
4. Redis Sentinel(主从切换)
- Leader选举
- 故障检测
- 自动切换
Paxos应用
1. Google Chubby(分布式锁)
- Paxos保证锁的一致性
- Multi-Paxos优化
2. Google Spanner(分布式数据库)
- 数据复制一致性
- 使用改进的Paxos
3. Zookeeper(类Paxos)
- ZAB协议(类似Paxos)
- 配置管理
- 分布式协调
💻 简化实现
Raft Leader选举(Go语言)
// Raft节点
type RaftNode struct {
id int
currentTerm int
votedFor int
state NodeState // Follower, Candidate, Leader
// 选举超时
electionTimeout time.Duration
lastHeartbeat time.Time
// 其他节点
peers []*RaftNode
// 日志
log []LogEntry
// 锁
mu sync.Mutex
}
// 节点状态
type NodeState int
const (
Follower NodeState = iota
Candidate
Leader
)
// 日志条目
type LogEntry struct {
Term int
Index int
Data interface{}
}
// 选举超时,转为Candidate
func (n *RaftNode) startElection() {
n.mu.Lock()
defer n.mu.Unlock()
// 增加任期
n.currentTerm++
n.state = Candidate
n.votedFor = n.id
fmt.Printf("节点%d发起选举,term=%d\n", n.id, n.currentTerm)
// 投票给自己
votes := 1
// 请求投票
for _, peer := range n.peers {
go func(peer *RaftNode) {
if peer.requestVote(n.currentTerm, n.id) {
n.mu.Lock()
votes++
// 获得多数票
if votes > len(n.peers)/2 && n.state == Candidate {
n.becomeLeader()
}
n.mu.Unlock()
}
}(peer)
}
}
// 处理投票请求
func (n *RaftNode) requestVote(term int, candidateId int) bool {
n.mu.Lock()
defer n.mu.Unlock()
// term太旧,拒绝
if term < n.currentTerm {
return false
}
// 发现更高的term,更新自己
if term > n.currentTerm {
n.currentTerm = term
n.state = Follower
n.votedFor = -1
}
// 如果还没投票,投给candidate
if n.votedFor == -1 || n.votedFor == candidateId {
n.votedFor = candidateId
fmt.Printf("节点%d投票给节点%d\n", n.id, candidateId)
return true
}
return false
}
// 成为Leader
func (n *RaftNode) becomeLeader() {
n.state = Leader
fmt.Printf("节点%d成为Leader,term=%d\n", n.id, n.currentTerm)
// 开始发送心跳
go n.sendHeartbeats()
}
// 发送心跳
func (n *RaftNode) sendHeartbeats() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for n.state == Leader {
<-ticker.C
for _, peer := range n.peers {
go peer.appendEntries(n.currentTerm, n.id, n.log)
}
}
}
// 接收心跳/日志
func (n *RaftNode) appendEntries(term int, leaderId int, entries []LogEntry) bool {
n.mu.Lock()
defer n.mu.Unlock()
// 更新心跳时间
n.lastHeartbeat = time.Now()
// term太旧,拒绝
if term < n.currentTerm {
return false
}
// 发现更高的term,转为Follower
if term > n.currentTerm {
n.currentTerm = term
n.state = Follower
n.votedFor = -1
}
fmt.Printf("节点%d收到Leader%d的心跳,term=%d\n", n.id, leaderId, term)
return true
}
🎉 总结
核心区别
1. 设计目标
Paxos:理论完备性
Raft:可理解性
2. 角色设计
Paxos:Proposer, Acceptor, Learner(复杂)
Raft:Leader, Follower, Candidate(清晰)
3. Leader
Paxos:可以有多个Proposer竞争
Raft:强Leader模型,同一时刻只有一个Leader
4. 日志
Paxos:日志可以有空洞
Raft:日志连续,不能有空洞
5. 实现
Paxos:需要自己补充很多细节
Raft:论文提供详细实现指导
选择建议
选Raft的场景:
✅ 新项目
✅ 团队理解分布式共识不深
✅ 需要快速实现
✅ 有现成的Raft库可用(etcd, Consul)
✅ 推荐 ⭐⭐⭐⭐⭐
选Paxos的场景:
✅ 已有Paxos实现
✅ 理论研究
✅ 特殊场景(如需要多个Proposer)
记忆口诀
共识算法解难题,
分布式系统达一致。
Paxos理论很完备,
两阶段协议晦涩难懂。
Proposer提出提案,
Acceptor进行投票,
Learner学习结果,
Multi-Paxos性能好,
但实现细节要补充。
Raft设计更清晰,
强Leader模型简单。
Leader处理写请求,
Follower跟随复制,
Candidate发起选举,
日志复制保一致。
三个子问题分解好:
Leader选举要投票,
日志复制保顺序,
安全性有保证!
Paxos偏理论,
Raft偏工程。
新项目用Raft,
易懂又易实现!
愿你的分布式系统达成共识,数据永远一致! 🗳️✨