Lab3 记录

Part 3A: leader election

1.选举主要流程

  1. 新服务器加入集群

服务器在启动时状态是Follower。只要持续接收到Leader或Candidate的心跳信息,就继续保持Follower状态。

  1. 开始选举

每个Server都有一个随机的选举超时时间,选举超时在一个固定区间内随机选择(例如,150-300毫秒)

如果Follower在选举超时时间内未收到有效的心跳信息,就认为当前没有有效Leader,转换自己为Candidate,发起选举。

心跳消息:一般是不包含日志条目的AppendEntries RPC

  1. 投票规则

服务器收到RequestVote RPC时,根据以下规则进行投票:

服务器以先到先得的方式投票,且每个任期只能投一票。

  • 如果收到的RPC的任期小于服务器当前的任期,拒绝投票
  • 如果服务器已经为当前任期投过票,或收到的RPC中的日志不如自己的日志新(最后一个条目的任期较旧或者任期相同但是编号较小),拒绝投票。
  • 如果以上情况都不满足,服务器会为Candidate投票,并重置自己的选举超时时间。
  1. 选举过程
  • 若选举过程中,Candidate收到其他Leader的心跳信息,且该Leader的任期大于等于候选人的任期,那么Candidate会承认新Leader并转回Follower状态;反之继续进行选举。
  1. 选举结果
  • 若Candidate获得的选票超过半数(3/5),则赢得选举,成为新Leader。同时向所有Server发送心跳消息,以防止新的选举。
  • 若选举超时,没有选出Leader,即没人获得多数票,一般是因为同时有多个Follower成为了Candidate。这种情况下,Candidate会等待一个随机的选举超时时间,增加任期,重新开始新一轮选举。

2.各组件逻辑

  1. 各组件都要遵守的规则

    任意RPC中包含的term大于自身term,则更新自己的term,并将自己转为follower状态,本节主要是心跳RPC和RequestVote RPC

  2. 随机超时检测ticker()

    功能:检测是否需要进行选举,使用Goroutine并行运行

    • role == Leader:跳过所有检测

    • role != Leader:

      若未收到心跳消息或未投票给任何人,举行选举

  3. 接收到心跳消息

    1. 如果心跳Term<当前Term,拒绝心跳,直接返回

      心跳TermTerm==当前Term心跳TermTerm>当前Term,则重置超时检测

    2. role==Candidaterole==Leader&&心跳Term>当前Term

      • 转回Follower

      • 更新当前Term为心跳Term

      • 更新LeaderID为心跳发送方

      • 更新VoteFor为心跳发送方

  4. 接收到投票请求

    说明已经有人开始选举,因此重置超时检测器,推迟自身可能要开始的选举

    1. 如果请求投票RPC Term<当前Term,拒绝

    2. 如果请求投票RPC Term>当前Term,投票

      更新自身Term

      如果当前Role!=Follower,转回Follower

    3. 如果请求投票RPC Term==当前Term,且当前Term没有投过票,投票

  5. 接收心跳超时,开始选举

    1. 配置自身角色

      1. Role切换为Candidate
      2. 投票给自己
      3. 当前Term+1
    2. 向其他Server请求投票

      票数过半,当选Leader

      1. 切换Role为Leader
      2. 更新LeaderID为自己
      3. 启动心跳发送程序

3.结果

测试6K次

image-20240901184324743

单次用时

image-20240924012301525

代码地址:https://github.com/INnoVationv2/6.5840/tree/Lab-3A-Dev/src/raft

go test -run 3A -count 1000 -failfast -timeout 3000m -race

Part 3B: log

  1. 完善结构体,将图2中State、AppendEntries RPC、RequestVote RPC的所有参数都写进代码

  2. 先从5.3节开始,实现不出错情况下Raft的处理逻辑,从Leader当选后开始

    当Leader收到客户端提交的命令时

    1. 根据客户端命令创建新的日志条目,日志条目由命令和任期编号组成。
    2. 向所有Follower并行发送AppendEntries RPC,其中包含待复制的旧条目、新条目、前一个日志条目的索引任期
    3. Follower收到AppendEntries RPC后,先检查其中的前一个日志条目的索引任期是否与自己的日志匹配。若匹配,则添加新条目并回复成功。若不匹配,则拒绝并回复失败。
    4. 当Leader收到包括Leader在内超过半数Follower的确认后,日志条目就被认为是safely replicated
    5. 之后,Leader执行条目到自己的状态机,然后将已执行的最大日志目录编号Index记录下来,下次与Follower通信时,会将其附上,这样Follower就知道Index之前的条目都已执行,然后Follower就也会执行这些条目到自己状态机。
    6. 将日志条目执行到状态机后,返回操作结果给客户端。
  3. 接下来实现日志修复逻辑

    Leader发生崩溃时,可能会导致日志不一致,旧Leader可能没有完全复制日志到其他服务器,Follower可能缺少leader的条目、也可能有Leader没有的条目。

    在Raft中,Leader会强制Follower复制自己的日志以保持一致。Follower日志中的冲突条目会被Leader日志覆盖。

    日志冲突处理步骤:

    image-20230425175616580
    1. Leader为每个Follower维护一个nextIndex,这是Leader下一次给Follower发生日志条目的起始编号
    2. Leader首次当选时,将所有nextIndex值初始化为日志中最后一个值之后的索引(图7中的11)。如果Follower与Leader的日志不一致,下一次AppendEntries RPC肯定会失败。失败后,Leader减少nextIndex并重试AppendEntries RPC。最终,nextIndex将达到和Follower日志匹配的点。
    3. 成功匹配后,AppendEntries将成功,Follower将删除日志中的所有冲突条目,并从Leader日志中追加条目。一旦AppendEntries成功,Follower与Leader的日志将一致,并且在该Term内始终保持一致。
  4. 继续实现论文中,为保证Raft正确性所做的一系列限制

    1. 选举限制

      防止未包含所有已提交条目的Candidate赢得选举

      收到投票请求时,投票人会将Candidate日志和自己的日志进行对比,

      • 如果Candidate的日志和投票人的至少一样新,投票
      • 如果投票人的日志比Candidate更新,不投票

      日志的定义:通过比较日志中最后一个条目的Index和Term来确定。

    2. 前一个Term的日志项

      对前一个Term的日志,不通过计算副本数规则提交,只对当前Term的日志项通过计算副本数提交,因为根据Log Matching属性,所有之前的条目都会被间接提交。

注:最近进行过日志发送或正在进行日志发送的话,本轮发送心跳暂停

一些坑

  1. 当发送非心跳消息到Follower超时时,若还是Leader,且term未变,需要持续重试

  2. Follower在接收到AppendEntries时,更新CommitIndex同样要遵循只提交当前任期日志项的原则

  3. Leader发送成功,更新NextIndex和MatchIndex时,先比较大小再更新,因为发的早的消息可能回复的比发的晚的消息更早

    如下两条消息

    {PrevLogIndex:5 PrevLogTerm:2 LeaderCommit:5 Len:4}
    {PrevLogIndex:5 PrevLogTerm:2 LeaderCommit:5 Len:5}
    

    第一条回来,NextIndex应该更新为10,第二条回来,NextIndex应该更新为11

    但是第二条如果回来的比第一条晚,NextIndex就会->11->10,从而出错

  4. 创建AppendEntries时,使用Copy,而不是切片引用,会出现DataRace

结果

3B测试100次

image-20240923161109298

单次用时

image-20240924012653056

Part 3C: persistence

这章就是在Persistent state发生改变时,及时持久化

如果测试报错,99%都是前两节没有写好

结果

3C测试100次

image-20240924012047002

单次用时

image-20240924012711612

Part 3D: log compaction

  1. Snapshot的定义:snapshot中保存的是已Commited日志的压缩,比如x+2 x-1,则Snapshot只需要存储x=1

  2. 当接收到Snapshot时,可以直接将其发送给tester,无需等待CommitIndex的确认,因为Leader的CommitIndex一定大于Snapshot

  3. Snapshot中保存的是已Commited日志,所以和Commit日志一样,只能向前推进,不能后退

    关于这一点有一种情况需要处理

    三个Server

    Leader最大Log Index为140,snapshop包含Index135之前的日志

    Follower1 和Leader同步

    Follower2 最大Log Index也是140,但是Snapshot只包含Index 120之前的日志

    之后Leader失效,Follower2当选Leader,发送snapshot给Follower1,Follower2已经有了更新的snapshot,所以没有更新

    Follower2将nextIndex[1]更新为121,下次发送日志prevLogIndex就是120,此时Follower会发现找不到Index 为120的日志

结果

测试100次

image-20240924231617401

单次用时

image-20240924232051965
posted @ 2024-09-24 23:41  INnoVation-V2  阅读(9)  评论(0编辑  收藏  举报