实现raft

     前段时间花了一周左右时间实现了raft,最后测试点全通过之后也是成就满满~ 虽然最后2C部分整得我头皮发麻....

这里记录一下实现raft需要注意的点,避免大家踩相同的坑....

    指导老师在课程安排中明确指出让我们不要把源码公布到网上,希望大家能自己克服困难,自己实现

先放两张通过测试点的截图,就很开心+-+

总体建议:

     1:实现raft主要参照了MIT的6.824课程的lab2以及指导论文,有条件的也最好看英文版,以及lab的lecture,我的建议是,动手实现算法之前一定要把论文摸透,保证所有内容都能看懂,

特别是Figure2的那张总体设计图,把preindex,nextindex,lastindex,comitindex,applyindex区分开来,这样实现算法的时候才不容易出大错误,不过不一定要全按照他的

设计来,实现的时候在其的基础上增加一些可能用到的数据来优化也是可以的.lecture的Go thread and raft部分是助教的建议,可以帮助我们debug,后面的Fault Tolerance: Raft部分

是教授的理论讲解,也很有必要看看.

     2:实验用的go语言,lab2为我们准备好了原始的rpc框架,我们只需要关注raft算法的具体实现,省去了很多影响因素。没用过go也不用怕,我做lab之前也不会go,go学习也不难,和java,c++很类似,有基础的两三天就能上手,重点关注分片,管道,go func以及Timer的使用

     3:我在初始化时就把log长度增加了1,这样访问以及发送数据时就都从下标为1的开始操作,不用考虑更多边界,参加过acm的同学应该都懂=-=

     4:处理请求时多判断当前状态,比如发送选举请求的时候自己状态已经改变了,就及时停止选举

     5.前面pass的部分不一定都正确,后面2C有超级测试点

2A:

    2A需要实现Leader选举,是最简单的部分,包括设定超时时间,超时选举,发送选举请求,接受选举请求,接受选举回复,选举Leader

    定时器有多种实现方法,可以用Go语言的Timer,也可以用所有语言都支持的sleep()方法,我用的Timer,因为好理解也不容易出错

lab2中给出的初始化函数make中就已经自动调用了它提供的ticker函数,你只需要在里面实现你的选举超时请求就可以了

func (rf *Raft) ticker() {
   rf.electionTimer=time.NewTimer(electionTimeout)
   for rf.killed() == false {
      <-rf.electionTimer.C
      
      //判断if....
      
      //超时选举
      
      //刷新RPC
      rf.flashRpc()
   }
}

关于随机数的获取:

rand.Int63()%(electionTimeoutTop-elctionTimeoutDown) + elctionTimeoutDown

选举时需要受到所有请求回复或者大部分选票才停止选举,可以用助教说的Broadcast以及wait()方法来使用,和java的wait和notify很像,这样就不可以避免用循环优化cpu性能

  加锁建议:

  RPC请求不要加锁!!!,血的教训呜呜呜,我最开始实现都是一把全局大锁,从头锁到尾,直到自己跑起来才发现大概率会触发死锁, 

  原理是在发送RPC请求时获取锁,在没有收到回复之前,可能受到别人的RPC请求,两边都在等待自己的发送请求函数释放锁,结果就无法处理

go func(server int) {
   //sendRpc
   rf.lock()
   defer rf.unlock()
   //reciveRpc
   finish++
   cond.Broadcast()
}(i)

go func带参数,可以避免i值改变而影响函数内部

2B:

2B为日志复制,需要实现Leader心跳,以及发送消息,处理回复,提交日志等

  1.Leader心跳也需要自己去实现一个Ticker,超时就发送心跳

   2.Leader发送消息不需要等待,我最开始以为是和选举一样,需要等待受到所有人的请求或者大部分人的ok回复才结束,其实不需要,而且日志复制肯定是尽可能多地复制到flower上,所以纯属自己犯傻.

   3.最好单独实现beflower,becandidate以及beleader的三个状态改变函数,可以在实现时降低出错概率

   4.论文中提到过一个优化,就是复制日志失败后,下一个发送位置nextidx[不采取-1政策,而是直接在response中加入有效的idx,直接把nextidx定位到返回的idx上,我的实现中也采取了这种优化,匹配不成功直接返回已正确提交的日志长度,这样就能减少RPC请求次数,增强网络通信质量

3C:

     2A以及2B结束raft算法其实已经结束了,3C主要是持久化,目的是为了避免服务器停电等常见事故,如果从头开始重新复制日志就会很慢,会耗费许多资源,需要持久化来优化,持久化的三个属性,已经复制了的日志log,当前term,以及投票结果votefor,前两个持久化都比较好理解,就是保存数据,

    那保存投票结果是为了什么呢?   这是为了防止服务器在收到选举请求并投票后立即拓机,后马上恢复过来,但是投票结果并没有持久化,收到其他候选人的RPC后再次投票,就可能导致一轮中有两个Leader,这是完全不被允许的.

实现持久化中我们需要完善读取和读出的两个函数,然后在上述三个属性更改时进行持久化即可,操作非常简单,但是3C的测试点变得非常复杂,大概率在3C会检测出之前两个功能的问题,需要再回头调试.

最后,希望大家能完成属于自己的raft

posted @ 2021-07-26 12:16  cono奇犽哒  阅读(156)  评论(0编辑  收藏  举报