选举
触发选举
Follower 在timeout时间段内没收到任何消息,认为没有存活的Leader,触发选举。
选举流程:
- 自增Term,给自己投一票
- 并行向其他节点发送RequestVote,等待下面三种情况之一:
-
超过半数票,赢得选举
-
有其他服务器确立了自己的领导地位
-
一段时间过去了,没有赢家。这些结果将在下文各段分别讨论。
什么时候触发再次选举?
Candidate超时后,再次触发
任何保证单主?
-
Leader必须赢得超过半数的选票
什么是投票分裂,如何避免?
多个Candidate同时请求选票,导致所有人都没有获得足够的票数
避免办法:
-
每次超时都随机选取超时时间,避免撞车
RequestVote
输入参数:
- term:新的任期
- Id:申请的候选人ID
- lastLogIndex:候选人最后一次日志条目的索引
- lastLogTerm:候选人最后一次日志记录的任期
返回结果:
- vote:true代表投,false代表不投
判断过程:
-
term<currentTerm,返回flase
-
votedFor为空 && 候选者日志不能比当前节点旧,返回true
-
否则返回flase
-
AppendEntries 心跳
输入参数:
- term:leader的term
- Id:leader的ID,follower可重定向请求到此
返回结果:
- pong:心跳的响应
处理过程:
-
term<currentTerm,返回flase
-
如果日志在prevLogIndex中不包含与prevLogTerm匹配的条目,则返回false
-
如果现有条目与新条目冲突(相同的索引但不同的term),则删除现有条目及其后的所有条目
-
追加日志中没有的任何新条目
-
如果leaderCommit > commitIndex,则设置commitIndex = min(leaderCommit,最后一个新条目的索引)
心跳的处理
-
Follower:更新Term
-
Candidate:Term>=当前term,放弃竞选,变为Follower
-
Leader:Term>当前term,变为Follower
日志复制
客户端消息的处理
-
leader接收:直接处理
-
Follower接收:转发给leader
-
节点宕机,换一台去请求
复制流程
- Leader记录entry到本地log
- 并发通过AppendEntries把日志发送给Follower
- 超过半数复制,Leader本地提交并广播,返回客户端结果
复制时的异常情况:
-
Follower缓慢:不断重复发送请求
-
Follower崩溃后恢复:一致性检查+日志修复
AppendEntries & 一致性检查
Leader状态记录在:
-
nextIndex:每个 server下一次要发送的entry 的 index
- 初始化本机最大 index+1
-
matchIndex:每个 server 已匹配的 entry 的 index
- 初始值0
-
commitedIndex:本机已提交的 index
- 初始值0
AppendEntriesRequest包含的字段:
- 基本字段:term、ID
- prevIndex:前一个数据的 index
- prevTerm:前一个数据的 term
- entries:本次复制的 entry
- commitedIndex:本次确认提交的 index
AppendEntriesResponse包含的字段:
-
success:复制状态
-
matchIndex:匹配的最大index
Leader 对 Server 发送的内容:
不同状态的发送:
-
nextIndex > 本地最大 index :说明已经是最新数据
-
返回:
- entries:当nextIndex==matchIndex-1时为为空
- commitedIndex:本地commitedIndex
-
-
nextIndex == 本地最大 index :需要同步最新的提交
-
返回:
- entries:当nextIndex==matchIndex-1时为logs[nextIndex]
- commitedIndex:本地commitedIndex
-
-
nextIndex < 本地最大 index:follower 数据落后,进行数据修补
-
返回:
-
entries:当nextIndex==matchIndex-1时为logs[nextIndex]
-
commitedIndex:max(本地commitedIndex,nextIndex)
-
-
Server 接收请求后的处理:
-
校对term:
-
term<currentTerm,返回flase
-
term>currentTerm,返回修改本地term
-
-
prevIndex 和 prevTerm 与本地匹配:
-
entries非空:删除本地prevIndex后面多余的数据,将entries追加到本地logs
-
更新本地commitedIndex
- 若commitedIndex > 本地commitedIndex:对commitedIndex以及之前的entries提交,更新本地commitedIndex
-
返回:true、本地commitedIndex
-
-
prevIndex 和 prevTerm 与本地不匹配:
-
返回:false、0
-
Leader 接收返回状态后的操作:
-
Server返回true:
- 更新nextIndex:commitedIndex + 1
- 更新matchIndex:commitedIndex
-
Server返回false:
-
更新 nextIndex = nextIndex - 1
-
安全性
选举限制
防止数据落后的节点成为Leader:
-
follower只给数据不比自己旧的节点投票
-
数据正常提交,大于一半节点commit,follower要想赢得选举需要获得大于一半的选票,也就是必须得到所有已提交数据的节点的同意,也就是自己不能缺失已提交的数据。保证了Leader数据最新。
Leader宕机处理:新leader不能提交之前任期内的日志条目
当Leader发送复制log还没提交就宕机了,恢复后又成为Leader,之前任期没提交的数据不能直接提交。
-
因为直接提交后,其他节点再当主时,可能会直接删除已提交的数据,造成数据丢失
-
应该在下一个entry提交时,自动提交前面的,也就是有最新Term进行担保,防止日志被删除
问题
-
双主:网络分区
小细节
-
lasttime更新时机:
-
每次收到消息
-
每次收到有用的消息
-
-
Leader 日志复制到大多数节点后,回复了客户端,但是在通知其他节点提交前宕机
- 也就是客户端显示提交成功,实际上还没有立即成功,需要新Leader提交后续entry时自动提交之前的
- 解决办法:集群超过半数节点都完成提交,再返回