【MIT 6.824】lab2:Raft选主、日志复制、容错实现流程

144 阅读4分钟

选举

触发选举

Follower 在timeout时间段内没收到任何消息,认为没有存活的Leader,触发选举。

选举流程:

  1. 自增Term,给自己投一票
  2. 并行向其他节点发送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时自动提交之前的
    • 解决办法:集群超过半数节点都完成提交,再返回