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 失效后的关键步骤。选举过程遵循随机超时与多数票原则。

  1. 触发选举:Follower 在选举超时(通常 150~300ms 随机)内未收到 Leader 的 AppendEntries RPC(心跳),则增加当前 Term,转换为 Candidate。
  2. 发起投票:Candidate 先给自己投一票,然后向集群中所有其他节点并行发送 RequestVote RPC。
  3. 投票规则:每个节点在每个 Term 内只能投出一票,且只有 Candidate 的日志至少与投票节点一样新(通过 lastLogTerm 和 lastLogIndex 比较),才会投赞成票。
  4. 胜出条件:Candidate 收到集群中多数节点(N/2 + 1)的赞成票后,成为该 Term 的 Leader。
  5. 平局与重试:若出现多个 Candidate 瓜分选票(Split Vote),则每个 Candidate 等待一个随机超时后重新发起选举。
Raft Leader 选举流程 Follower Candidate Leader 选举超时 Term++ 获得多数票 发现更高 Term RequestVote RPC 交互 Candidate Follower A Follower B 请求投票 请求投票 赞成票 拒绝(日志不够新)
关键参数:etcd 中 --election-timeout 默认 1000ms,--heartbeat-interval 默认 100ms。选举超时通常设置为心跳间隔的 5~10 倍,以避免频繁的 Leader 切换。

2.3 日志复制(Log Replication)

一旦 Leader 当选,它开始处理客户端请求。每个客户端请求被封装为一个日志条目(Log Entry),包含命令(如 PUT key=value)和 Term 编号。日志复制流程如下:

  1. 追加本地:Leader 将日志条目追加到自己的日志中。
  2. 并行发送:Leader 向所有 Follower 发送 AppendEntries RPC,包含新日志条目及前一条日志的索引和 Term。
  3. 一致性检查:Follower 检查 prevLogIndex 和 prevLogTerm 是否与自己的日志匹配。若匹配,则追加新条目;若不匹配,则拒绝并返回冲突信息。
  4. 提交(Commit):Leader 收到多数 Follower 的成功响应后,将日志条目标记为已提交(committed),并应用到状态机(State Machine)。
  5. 通知应用:Leader 在后续的 AppendEntries RPC(心跳)中携带 commitIndex,通知 Follower 哪些日志可以安全应用到状态机。
日志复制流程(Log Replication) Leader 日志 Term 1 Term 1 Term 2 Term 3 Term 3 Term 4 Term 4 已提交(committed) Follower 日志(复制中) Term 1 Term 1 Term 2 Term 3 Term 3 Term 4 待复制 AppendEntries 状态机(State Machine) 应用已提交日志:PUT key=value, DELETE key, CAS ... apply

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)机制进行修复:

  1. Leader 维护每个 Follower 的 nextIndex,初始化为 Leader 最后一条日志索引 + 1。
  2. 如果 AppendEntries 被拒绝(prevLogIndex 不匹配),Leader 将 nextIndex 减 1,重试。
  3. 重复步骤 2,直到找到 Follower 与 Leader 匹配的日志位置。
  4. 从匹配位置开始,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 五条原则保证。
  • 日志修复 采用逐项回退策略,确保集群最终达到一致状态。