1.总体理解
raft是一个主从协议,需要leader。
就像是一堆人,有一个人负责写操作,并会定期同步消息给其他人,这个人就是leader。
但是当这个leader联系不上的时候,其他人就会每隔一段时间讨论一下让谁去当leader,进行选举,如果这个参选的人的消息(日志)太旧,其他人就会觉得他不如自己,就不会选他。被大多数人选的人就会成为下一个leader。
Candidate 先将本地的 currentTerm++,然后向其他节点发送 RequestVote 请求。其他节点根据本地数据版本、长度和之前选主的结果判断应答成功与否。具体处理规则如下:
如果 Time.Now() – lastLeaderUpdateTimestamp < electionTimeout,忽略请求;
如果 req.term < currentTerm,忽略请求;
如果 req.term > currentTerm,设置 currentTerm = req.term。如果是 Leader 和 Candidate 转为 Follower;
如果 req.term == currentTerm,并且本地 voteFor 记录为空或者是与 vote 请求中 term 和 CandidateId 一致,req.lastLogIndex > lastLogIndex,即 Candidate 数据新于本地则同意选主请求;
如果 req.term == currentTerm,如果本地 voteFor 记录非空或者是与 vote 请求中 term 一致 CandidateId 不一致,则拒绝选主请求;
如果 req.term == currentTerm,如果 lastLogTerm > req.lastLogTerm,本地最后一条 Log 的 Term 大于请求中的 lastLogTerm,说明 candidate上数据比本地旧,拒绝选主请求。
currentTerm 只是用于忽略老的 Term 的 vote 请求,或者提升自己的 currentTerm,并不参与 Log 新旧的决策。Log 新旧的比较,是基于 lastLogTerm 和 lastLogIndex 进行比较,而不是基于 currentTerm 和 lastLogIndex 进行比较。
2.三种信息Remote Procedure Call
Raft 信息有 3 种 RPC:
RequestVote RPC :由 Candidate 发出,用于发送投票请求;
AppendEntries (Heartbeat) RPC :由 Leader 发出,用于 Leader 向 Followers 复制日志条目,也会用作 Heartbea(日志条目为空即为 Heartbeat);
InstallSnapshot RPC :由 Leader 发出,用于快照传输。虽然多数情况都是每个服务器独立创建快照,但是 Leader 有时候必须发送快照给一些落后太多的 Follower,这通常发生在 Leader 已经丢弃了下一条要发给该 Follower 的日志条目(Log 压缩时清除掉了的情况)。
Log 达到一定大小、数量、超过一定时间可以做 Snapshot。;
如果底层存储支持 COW(Copy On Write),则可以使用 COW 做 Snapshot,减小对 Log Append 的影响。
3.Log 复制
大致流程是:更新操作通过 Leade r写入 Log,复制到多数节点,变为 Committed,再 Apply 业务状态机。
Raft 依赖 Leader 来保持集群的数据一致性,数据的复制都是从 Leader 到 Follower。一个简单的写入流程如下,性能是完全不行的:
Leader 收到 Client 请求;
Leader 将数据 Append 到自己的 Log;
Leader 将数据发送给其他的 Follower;
Leader 等待 Follower ACK,大多数节点提交了 Log,则 Apply;
Leader 返回 Client 结果;
Leader 跟其他节点之间的 Log 同步是串行 Batch 的方式,如果单纯使用 Batch,每个Batch 发送之后 Leader 依旧需要等待该 Batch 同步完成之后才能继续发送下一个 Batch,这样会导致较长的延迟。可以通过 Leader 跟其他节点之间的 PipeLine 复制来改进,会有效降低延迟。
4.prevote
在基础的 raft 算法中,当一个 follower 节点与其他节点发生网络分区时,由于心跳超时,会主动发起一次选举,每次选举时会把 term 加一。由于网络分区的存在,每次 RequestVote RPC 都会超时,结果是,一直不断地发起新的选举,term 会不断增大。
在网络分区恢复,重新加入集群后,其 term 值会被其他节点知晓,导致其他节点更新自己的 term,并变为 follower。然后触发重新选举,但被隔离的节点日志不是最新,并不会竞选成功,整个集群的状态被该节点扰乱。
Prevote 算法是 raft 作者在其博士论文中提出的,在节点发起一次选举时,会先发起一次 prevote 请求,判断是否能够赢得选举,赢得选举的条件与正常选举相同。如果可以,则增加 term 值,并发起正常的选举。