2. Raft共识算法基础:Leader选举机制、日志复制、安全性保证、Term与任期概念
Raft 是一种为可理解性(Understandability)而设计的分布式共识算法,其核心目标是在非拜占庭故障(即节点可能崩溃、丢包、延迟,但不会发送恶意消息)环境下,保证集群中各节点日志状态的一致性。etcd 正是基于 Raft 算法实现了高可用的键值存储。本节将拆解 Raft 的四大核心模块:Term(任期)、Leader 选举、日志复制与安全性保证。
2.1 Term(任期)与角色模型
Raft 将时间划分为任意长度的任期(Term),每个任期以一次 Leader 选举开始。Term 是一个连续递增的整数,充当逻辑时钟,用于检测过期信息。
| 角色 | 职责 | 状态转换触发条件 |
|---|---|---|
| Follower(跟随者) | 被动响应 Leader 和 Candidate 的请求;若未收到有效心跳,则超时后转为 Candidate。 | 启动时默认为 Follower;收到更高 Term 的请求时降级为 Follower。 |
| Candidate(候选者) | 发起选举,向其他节点请求投票;获得多数票后转为 Leader。 | 选举超时触发;收到更高 Term 的请求时降级为 Follower。 |
| Leader(领导者) | 处理所有客户端请求,管理日志复制,发送心跳(AppendEntries RPC)维持权威。 | 赢得选举后成为 Leader;发现更高 Term 时自动降级为 Follower。 |
Term 的作用:每个 RPC 请求和响应都携带 Term 字段。如果接收方发现请求中的 Term 小于自己的当前 Term,则拒绝该请求;如果发现请求中的 Term 大于自己的 Term,则立即更新自己的 Term 并转为 Follower。这种机制防止了网络分区恢复后的“脑裂”问题。
2.2 Leader 选举机制
Leader 选举是 Raft 启动或现有 Leader 失效后的关键步骤。选举过程遵循随机超时与多数票原则。
- 触发选举:Follower 在选举超时(通常 150~300ms 随机)内未收到 Leader 的 AppendEntries RPC(心跳),则增加当前 Term,转换为 Candidate。
- 发起投票:Candidate 先给自己投一票,然后向集群中所有其他节点并行发送
RequestVote RPC。 - 投票规则:每个节点在每个 Term 内只能投出一票,且只有 Candidate 的日志至少与投票节点一样新(通过 lastLogTerm 和 lastLogIndex 比较),才会投赞成票。
- 胜出条件:Candidate 收到集群中多数节点(N/2 + 1)的赞成票后,成为该 Term 的 Leader。
- 平局与重试:若出现多个 Candidate 瓜分选票(Split Vote),则每个 Candidate 等待一个随机超时后重新发起选举。
关键参数:etcd 中
--election-timeout 默认 1000ms,--heartbeat-interval 默认 100ms。选举超时通常设置为心跳间隔的 5~10 倍,以避免频繁的 Leader 切换。
2.3 日志复制(Log Replication)
一旦 Leader 当选,它开始处理客户端请求。每个客户端请求被封装为一个日志条目(Log Entry),包含命令(如 PUT key=value)和 Term 编号。日志复制流程如下:
- 追加本地:Leader 将日志条目追加到自己的日志中。
- 并行发送:Leader 向所有 Follower 发送
AppendEntries RPC,包含新日志条目及前一条日志的索引和 Term。 - 一致性检查:Follower 检查 prevLogIndex 和 prevLogTerm 是否与自己的日志匹配。若匹配,则追加新条目;若不匹配,则拒绝并返回冲突信息。
- 提交(Commit):Leader 收到多数 Follower 的成功响应后,将日志条目标记为已提交(committed),并应用到状态机(State Machine)。
- 通知应用:Leader 在后续的 AppendEntries RPC(心跳)中携带
commitIndex,通知 Follower 哪些日志可以安全应用到状态机。
2.4 安全性保证(Safety)
Raft 通过以下五条安全性原则保证集群在任意时刻只有一个有效的 Leader,且日志在所有节点上最终一致:
| 安全性属性 | 描述 | 实现机制 |
|---|---|---|
| Election Safety | 任意 Term 内最多只有一个 Leader 被选出。 | 每个节点在每个 Term 内只能投一票;多数票才能当选。 |
| Leader Append-Only | Leader 从不覆盖或删除自己的日志,只追加新条目。 | Leader 日志是权威来源,Follower 以 Leader 为准进行一致性修复。 |
| Log Matching | 如果两个节点上的日志条目具有相同的 index 和 Term,则它们的内容相同,且之前的所有日志也相同。 | AppendEntries RPC 携带 prevLogIndex/prevLogTerm 进行一致性检查。 |
| Leader Completeness | 被提交的日志条目在后续所有 Term 的 Leader 中一定存在。 | Candidate 必须拥有比多数节点更新的日志才能当选(通过 lastLogTerm 和 lastLogIndex 比较)。 |
| State Machine Safety | 如果某个节点将特定 index 的日志应用到状态机,则其他节点不会在该 index 应用不同的日志。 | 只有已提交(committed)的日志才能被应用;提交条件为日志已被复制到多数节点。 |
常见误区:Leader 选举不是“谁先发起谁赢”,而是“谁的日志更完整谁更容易赢”。一个日志落后的节点即使先发起选举,也会因为日志不够新而被其他节点拒绝投票。这保证了新 Leader 一定拥有所有已提交的日志。
2.5 日志一致性修复机制
当 Follower 日志与 Leader 不一致时(例如网络分区导致 Follower 落后或出现冲突条目),Leader 通过逐项回退(backward matching)机制进行修复:
- Leader 维护每个 Follower 的
nextIndex,初始化为 Leader 最后一条日志索引 + 1。 - 如果 AppendEntries 被拒绝(prevLogIndex 不匹配),Leader 将
nextIndex减 1,重试。 - 重复步骤 2,直到找到 Follower 与 Leader 匹配的日志位置。
- 从匹配位置开始,Leader 将后续所有日志条目发送给 Follower 进行覆盖。
性能影响:在 etcd 压测中,如果集群发生 Leader 切换,新 Leader 可能需要花费多个 RTT 来修复落后 Follower 的日志。这会导致写入延迟短暂升高,直到所有 Follower 的日志追上 Leader。这也是为什么 etcd 压测沙盘需要观察 Leader 选举后的吞吐恢复曲线。
2.6 本章小结
- Term 是 Raft 的逻辑时钟,每个 Term 对应一个 Leader 任期,用于检测过期信息。
- Leader 选举 依赖随机超时和多数票机制,保证快速收敛且不会出现双 Leader。
- 日志复制 通过 AppendEntries RPC 实现,Leader 负责协调,Follower 被动同步。
- 安全性 通过 Election Safety、Leader Append-Only、Log Matching、Leader Completeness 和 State Machine Safety 五条原则保证。
- 日志修复 采用逐项回退策略,确保集群最终达到一致状态。