Raft 分布式共识算法详解:从选举到日志复制
【摘要】 Raft 分布式共识算法详解:从选举到日志复制本文深入剖析 Raft 共识算法的核心原理,帮助你理解分布式系统如何达成一致性。 为什么需要共识算法?分布式系统中,多个节点需要就某个值达成一致,但面临:┌─────────────────────────────────────────────────────────┐│ 1. 节点可能崩溃(Crash Fault) ...
Raft 分布式共识算法详解:从选举到日志复制
本文深入剖析 Raft 共识算法的核心原理,帮助你理解分布式系统如何达成一致性。
为什么需要共识算法?
分布式系统中,多个节点需要就某个值达成一致,但面临:
┌─────────────────────────────────────────────────────────┐
│ 1. 节点可能崩溃(Crash Fault) │
│ 2. 网络可能分区、延迟、丢包、乱序 │
│ 3. 消息可能重复 │
│ │
│ 但假设:不存在恶意节点(非拜占庭容错) │
└─────────────────────────────────────────────────────────┘
共识算法目标: 即使部分节点故障,系统整体仍能对外提供一致、可用的服务。
典型应用: etcd、Consul、TiKV、CockroachDB、Kafka KRaft。
Raft 的核心思想:可理解性优先
Raft 把共识分解为三个相对独立的子问题:
1. Leader 选举(Leader Election)
2. 日志复制(Log Replication)
3. 安全性(Safety)
核心准则:任何时刻最多一个 Leader,
所有写入都经由 Leader 发起并复制到多数节点。
节点角色与任期
┌─────────────────────────────────────────────────────────┐
│ 三种角色 │
│ │
│ Follower ──超时未收心跳──→ Candidate ──获多数票──→ Leader │
│ ↑ │ │
│ └──────────────── 收到更高任期 ───────────────┘ │
│ │
│ Term(任期):单调递增的逻辑时钟,用 term 号区分 │
│ 不同选举周期,是判断信息新旧的核心依据 │
└─────────────────────────────────────────────────────────┘
角色职责:
| 角色 | 职责 |
|---|---|
| Leader | 接收写请求,复制日志,发送心跳 |
| Follower | 被动接收日志和心跳,超时则发起选举 |
| Candidate | 发起投票,争取成为 Leader |
Leader 选举
选举触发与流程
选举超时(Election Timeout):随机 150ms ~ 300ms
Follower 超时未收到心跳:
1. 自增 term,转为 Candidate
2. 投票给自己
3. 向所有节点发送 RequestVote RPC
4. 获多数票(N/2 + 1)→ 成为 Leader
若无多数票 → 超时后再次发起新选举
┌─────────┐ RequestVote ┌─────────┐
│Candidate│ ──────────────→│Follower │
│ term=2 │ ←──────────────│ 投票 │
└─────────┘ RequestVote └─────────┘
随机超时的作用
问题:若所有 Follower 同时超时,会瓜分选票导致无人过半(选票分裂)
解决:每个节点随机选举超时时间
→ 大多数情况下只有一个节点先超时成为 Candidate
→ 大幅降低选票分裂概率,即使分裂下次也能快速收敛
投票规则
Follower 投票给 Candidate 的条件(同时满足):
1. term 不小于自己当前 term(否则拒绝)
2. 自己本任期尚未投票
3. Candidate 的日志"至少不比自己旧"
(最后日志的 term 更大,或 term 相同但 index 更大)
→ 保证选出拥有最全日志的 Leader(安全性关键)
日志复制
正常流程
Client ──写请求──→ Leader
│
├─ 1. 追加到本地日志(uncommitted)
├─ 2. 并行发送 AppendEntries 给所有 Follower
├─ 3. 多数 Follower 写入成功
├─ 4. Leader 提交(commit)并返回客户端
└─ 5. 通知 Follower 提交
日志结构:
index: 1 2 3 4 5
term: 1 1 2 2 3
cmd: x=1 y=2 x=3 y=4 x=5
AppendEntries RPC 与一致性检查
AppendEntries 携带:
- term:Leader 任期
- leaderCommit:Leader 已提交位置
- prevLogIndex / prevLogTerm:前一条日志的位置与任期
- entries[]:要复制的日志
一致性检查:
Follower 校验 prevLogIndex 处的日志 term 是否匹配:
- 匹配:接受新日志
- 不匹配:拒绝,Leader 递减 nextIndex 重试
→ 逐条回溯,最终找到一致点,覆盖冲突日志
提交规则(安全性核心)
Leader 只能提交"当前任期"的日志!
关键限制:
即使旧任期日志被复制到多数节点,也不能仅凭副本数提交,
必须等当前任期有一条日志被复制到多数节点后,
才能连带提交之前的旧日志(commitIndex 前移)。
原因:避免"已提交日志被回滚"的悖论。
(论文中的 Figure 8 场景)
网络分区与脑裂处理
场景:5 节点分区分裂
分区 A(3 节点):有 Leader,可正常选举与提交
分区 B(2 节点):旧 Leader 无法获多数,无法提交
┌───────────────┐ ┌───────────────┐
│ 分区 A │ │ 分区 B │
│ ● ● ● (3) │ ╳ │ ● ● (2) │
│ 有多数,正常 │ │ 无多数,阻塞 │
└───────────────┘ └───────────────┘
恢复后:
分区 B 中 term 较低的节点收到更高 term → 回退为 Follower
冲突的未提交日志被 Leader 覆盖 → 系统重新一致
结论:多数派(Quorum)是可用性的分界线
5 节点容忍 2 个故障,3 节点容忍 1 个故障
成员变更与日志压缩
成员变更
问题:直接切换配置可能产生"双多数派",出现两个 Leader
解决(单节点变更法):
每次只增/删一个节点,保证任意时刻新旧配置多数派有交集
→ 不会同时选出两个 Leader
日志压缩(Snapshot)
问题:日志无限增长,重启回放慢
解决:Snapshot(快照)
1. 将当前状态机的状态持久化为快照
2. 快照覆盖 index 之前的日志可丢弃
3. 新节点通过 InstallSnapshot RPC 快速同步
index: 1..1000 → Snapshot(状态) + 日志[1001..]
对比 Paxos
| 维度 | Raft | Paxos |
|---|---|---|
| 设计目标 | 可理解性、工程友好 | 理论优雅 |
| Leader | 强 Leader,日志单向流动 | 弱化,多变体 |
| 日志 | 连续、强一致复制 | 允许空洞 |
| 教学/实现 | 易于理解和实现 | 晦涩,实现多样 |
| 代表 | etcd、TiKV | Chubby、Spanner |
实战:生产环境要点
# etcd 集群健康检查
etcdctl endpoint health --cluster
etcdctl endpoint status --cluster -w table
# 关键监控指标
# - leader 变更频率(频繁说明网络或负载异常)
# - 日志复制延迟
# - WAL fsync 延迟(磁盘是常见瓶颈)
部署建议:
- 节点数用奇数:3 或 5,兼顾容错与性能
- 跨机架/可用区部署:避免同故障域,但也别过度分散导致延迟高
- SSD 存储:WAL fsync 对磁盘延迟敏感
- 网络稳定:高延迟会触发频繁选举,影响可用性
- 监控选举与复制延迟:及时发现问题
总结
| 子问题 | 核心机制 |
|---|---|
| 选举 | 随机超时 + 多数投票 + term 比较 |
| 日志复制 | AppendEntries + 一致性检查 + 覆盖冲突 |
| 安全性 | 只提交当前任期日志;投票限制日志新旧 |
| 容错 | 多数派(Quorum),容忍 N/2 故障 |
| 变更 | 单节点变更避免双多数派 |
Raft 的精髓:用强 Leader 简化问题,用随机化和多数派保证安全,用 term 和日志匹配保证一致。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)