3. Raft共识算法基础
etcd 作为 Kubernetes 的存储基石,其高可用与强一致性依赖于 Raft 共识算法。Raft 是一种易于理解的分布式一致性协议,它将复杂的共识问题分解为三个相对独立的子问题:Leader 选举、日志复制和安全性保证。本节将深入剖析这三个核心机制,帮助你理解 etcd 集群如何在节点故障时保持数据一致。
3.1 Raft 节点状态与任期
在 Raft 集群中,每个节点(Server)在任意时刻都处于以下三种状态之一:
| 状态 | 角色 | 核心行为 |
|---|---|---|
| Follower | 跟随者 | 被动响应来自 Leader 和 Candidate 的请求;如果在一段时间内未收到 Leader 的心跳,则转换为 Candidate。 |
| Candidate | 候选者 | 发起选举,向其他节点请求投票;获得多数票后成为 Leader。 |
| Leader | 领导者 | 处理所有客户端请求,管理日志复制,定期向 Follower 发送心跳以维持权威。 |
Raft 使用 任期(Term) 作为逻辑时钟。每个任期以一次选举开始,如果选举成功,该任期内由唯一的 Leader 管理集群;如果选举失败(如选票分裂),则立即进入下一个任期并开始新的选举。
关键点:任期号单调递增,且在整个集群中全局唯一。节点在通信时会交换任期号,如果发现自己的任期号小于对方,则立即更新并转为 Follower。
3.2 Leader 选举机制
当集群启动或现有 Leader 宕机时,Raft 会触发 Leader 选举。选举过程遵循以下步骤:
- 发起选举:Follower 在“选举超时”时间内未收到 Leader 的心跳,则增加自己的任期号,转换为 Candidate,并投票给自己。
- 请求投票:Candidate 并行向集群中所有其他节点发送
RequestVote RPC,包含自己的任期号和日志信息。 - 投票规则:每个节点在每个任期内只能投出一张票,且遵循“先到先得”原则。节点只有在 Candidate 的日志至少与自己一样新时才会投票。
- 赢得选举:如果 Candidate 收到超过半数节点(
N/2 + 1)的投票,则成为该任期的 Leader。 - 心跳维持:新 Leader 立即向所有节点发送心跳(AppendEntries RPC,空日志条目),重置其他节点的选举超时计时器,从而稳定自己的领导地位。
避坑指南:选举超时时间通常设置为 150ms ~ 300ms 的随机值。这种随机性有效避免了多个 Follower 同时发起选举导致的“选票分裂”问题,确保集群能快速收敛出一个 Leader。
3.3 日志复制(Log Replication)
一旦 Leader 被选举出来,它就开始负责处理客户端的所有写入请求。日志复制是保证集群数据一致性的核心流程:
- 客户端请求:客户端将写操作发送给 Leader。
- 追加日志:Leader 将操作封装为一个日志条目(Log Entry),追加到自己的本地日志中。
- 并行复制:Leader 向所有 Follower 发送
AppendEntries RPC,包含该日志条目及前一条日志的索引和任期号。 - Follower 响应:Follower 收到 RPC 后,检查本地日志是否与 Leader 一致。如果一致,则将日志条目追加到本地,并返回成功。
- 提交(Commit):当 Leader 收到超过半数 Follower 的成功响应后,该日志条目被标记为“已提交”(Committed),并应用到状态机(State Machine)。
- 通知客户端:Leader 将执行结果返回给客户端。
- 后续心跳:Leader 在后续的心跳中通知 Follower 该日志条目已被提交,Follower 随后也将其应用到自己的状态机。
重要原则:日志条目只能从 Leader 流向 Follower,绝不允许反向流动。Leader 永远不会覆盖或删除自己的日志条目,这保证了日志的“Leader 完整性”。
3.4 安全性保证(Safety)
Raft 通过一系列安全性约束来保证集群在 Leader 选举和日志复制过程中不会出现数据不一致或数据丢失。核心安全性保证包括:
| 安全性属性 | 描述 | 实现机制 |
|---|---|---|
| Election Safety | 每个任期最多只有一个 Leader。 | 节点在一个任期内只能投一票,且只有获得多数票的 Candidate 才能成为 Leader。 |
| Leader Append-Only | Leader 从不覆盖或删除自己的日志条目,只追加新条目。 | Leader 的日志是权威来源,Follower 的日志必须与 Leader 保持一致。 |
| Log Matching | 如果两个节点上的日志条目具有相同的索引和任期,则它们存储相同的命令,并且之前的所有日志条目也一致。 | AppendEntries RPC 包含前一条日志的索引和任期,Follower 会进行一致性检查。 |
| Leader Completeness | 被选举为 Leader 的节点必须拥有所有已提交的日志条目。 | 投票时,Candidate 的日志必须至少与投票节点一样新(通过比较最后一条日志的任期和索引)。 |
| State Machine Safety | 如果某个节点将特定索引处的日志条目应用到状态机,则其他节点不会在该索引处应用不同的日志条目。 | 只有已提交的日志条目才能被应用到状态机,且所有节点以相同的顺序应用相同的已提交日志。 |
核心结论:Raft 的安全性保证确保了集群在任何故障场景下都不会返回不一致的数据。Leader 选举的“日志新度”检查是防止已提交日志丢失的关键防线。
3.5 集群成员变更(Cluster Membership Change)
在生产环境中,etcd 集群需要动态添加或移除节点。Raft 使用一种称为 联合共识(Joint Consensus) 的两阶段方法来实现安全的成员变更:
- 阶段一(C_old,new):Leader 将新配置(C_new)作为一个特殊日志条目复制到集群中。此时,集群进入一个过渡状态,同时使用旧配置(C_old)和新配置(C_new)进行决策。任何决策都需要获得 C_old 和 C_new 两个配置的多数同意。
- 阶段二(C_new):一旦 C_old,new 被提交,Leader 立即开始复制 C_new 配置。当 C_new 被提交后,集群完全切换到新配置,旧节点被安全移除。
实践建议:在 Kubernetes 环境中,etcd 集群的成员变更通常由 etcd-operator 或 kubeadm 自动管理。手动变更时,务必一次只添加或移除一个节点,避免同时进行多个变更导致集群分裂。