实现概述
Leader 选举
针对 leader 选举,etcd 基本是按照 Raft 协议中的方式实现的,其中有几点需要关注下,
-
etcd 默认的心跳间隔时间(heartbeat-interval)是 100ms, 默认 election timeout 时间是 1000ms。需要根据实际部署环境、业务场景适当调优,否则就很可能会频繁发生 Leader 选举切换,导致服务稳定性下降。
-
针对上述 “当 Node A 网络恢复之后,由于 Node B/C 发现 Node A 的 term 值更大,但是 Node A 并不是 leader,此时会进行重新选举。但是,此时 Node A 的状态是远远落后于 Node B/C 的,是无法获得 leader 的,此次的选举相当于是无效的” 的问题。
etcd 3.4 引入了一个 PreVote 参数(默认 false),该参数会启用 PreCandidate 状态,通过 PreCandidate 状态来解决该问题。Follower 在转换成 Candidate 状态前,会先进入 PreCandidate 状态,此时不自增任期号,仅发起预投票。若获得集群多数节点认可,确定有概率成为 Leader 才能进入 Candidate 状态,发起选举流程。那么,假设 Node A 节点数据落后较多,预投票请求是无法获得多数节点认可的,因此它就不会进入 Candidate 状态,导致集群重新选举。
日志复制
Etcd 中日志复制的实现与 Raft 中的流程类似,略有不同的是 Etcd 增加了 WAL 日志模块。整体流程如下所示,
-
Leader 收到客户端的请求后,KV 模块会向 Raft 模块提交一个 put hello 为 world 的 proposal。采用的消息类型是 MsgProp。
-
Raft 模块收到 MsgProp 提案消息后,
-
会为此提案生成一个日志条目,并先追加到 unstable 存储中(不是下面的 Raft 日志存储,这里相当于 Raft 模块内部的一个缓存)。
-
随后会遍历集群 Follower 列表和进度信息,为每个 Follower 生成追加(MsgApp)类型的 RPC 消息,此消息中包含待复制给 Follower 的日志条目。
-
同时会维护两个核心字段来追踪各个 Follower 的进度信息,一个字段是 NextIndex, 它表示 Leader 发送给 Follower 节点的下一个日志条目索引,另一个字段是 MatchIndex,它表示 Follower 节点已复制的最大日志条目的索引。比如下图中,Follower C 的 MatchIndex 为 5,Follower A 的为 4。
-
-
从 Raft 模块获取待持久化的日志条目、待发送给 Follower 节点的消息、已提交的日志目录、HardState (都包含在 Ready 结构中)等内容。
-
将日志条目持久化到 WAL 日志模块中,也会将已知的最大已提交日志条目的索引(committed index)、当前任期号、当前任期中投票的候选者 ID(都在 HardState 结构中)都会被写入到 WAL 中。
-
将日志条目追加到 Raft Stable 存储(内存存储)中。
-
通过网络模块发送消息(MsgApp)给 Follower,也会包含当前已知的最大已提交日志条目的索引。
-
将已提交的日志条目通过 Apply 模块,应用到存储状态机。
-
-
各个 Follower 收到追加日志条目(MsgApp)消息后,
- 它会持久化日志条目到 WAL 日志模块中,并将消息追加到 Raft Stable 存储(内存存储)中。
- 随后向 Leader 回复一个应答追加日志条目(MsgAppResp)的消息,告知 Leader 当前已复制的日志最大索引。
-
Leader 收到应答追加日志条目(MsgAppResp)消息后,Raft 模块会将 Follower 回复的已复制日志最大索引更新到跟踪 Follower 进展的 MatchIndex 字段。如下图所以,Follower C 的 MatchIndex 为 6,Follower A 为 5。
-
Raft 模块根据所有 follower 的 MatchIndex 信息,计算出一个位置,如果这个位置已经被一半以上节点持久化,那么这个位置之前的日志条目都可以被标记为已提交。如下图所示,6 号索引位置之前的日志条目都已被多数节点复制,那么最大已提交的日志条目索引可更新为 6。
-
由于 Leader 从 Raft 模块中取 Ready 结构相当于是一个循环操作,因此此时就会按照取出 Ready 结构后的操作执行。这里主要关注的是将已提交的日志条目通过 Apply 模块,应用到存储状态机,同时将已提交的日志条目索引发送给 Follower。
相关链接
-
Raft Example:https://github.com/etcd-io/etcd/blob/main/contrib/raftexample/README.md
-
Etcd Raft库的工程化实现:https://www.codedump.info/post/20210515-raft
-
etcd Raft库解析:https://www.codedump.info/post/20180922-etcd-raft/