热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

这可能是全网最透彻的raftelection+hearbeat(mit6.824parta)代码解析(1)

前言:虽然mit6.824这门课说不让大家把代码公布出来,因为它们觉得自己写学习到的才是最多的。我不否认它们这个观点,但当一个go实践并不

前言:
虽然mit6.824这门课说不让大家把代码公布出来,因为它们觉得自己写学习到的才是最多的。我不否认它们这个观点,但当一个go实践并不多的学生开始写的时候,它会发现毫无思路。因此,对于一些想学raft并实践的学生(比如说我),是非常需要一份详细到不能再详细的代码解析的。然而,现状是网上的raft 各个part解析都是只讲重点而不注重细节,因此我认为很有必要把细节缕清楚!

参考资料:
我这里的代码是参考thu大神的,但这位小哥哥也没有详细地解释清楚,我就借花献佛,将它的代码逻辑讲明白
在实现之前,可以先参考我之前写的一些博客,包括raft动图的介绍以及论文翻译:
详见我的论文学习模块
mit 6.824 lab2 raft

内容概括:
本文主要从以下几个方面展开对parta实验的源码解析:
1.raft结构体
2.make初始化
2.5 changestate()函数的实现
3.requestvoterpc的实现
4.appendentries的实现(仅heartbeat)
5.ticker的逻辑
6.测试样例的讲解与测试结果的解析(正常选取,再选举,多节点宕机然后再进入)

preparation
一些常数的设定:

// 常量的设置,包括状态对应的int,以及timeout时间
const (FOLLOWER = 0CANDIDATE = 1LEADER = 2TO_FOLLOWER = 0TO_CANDIDATE = 1TO_LEADER = 2// 设置选举时间的随机区间ELECTION_TIMEOUT_MAX = 600ELECTION_TIMEOUT_MIN = 400// 设置心跳的timeoutHEARTBEAT_TIMEOUT = 100// 设置apply的timeout// APPLIED_TIMEOUT = 28
)

设置follower、candidate、leader三个状态对应的int,以及心跳的timeout为100ms(raft实验要求1s不超过十次心跳),这样我们就要适当增加论文中150-300ms的electiontimeout的限度了,这里设置为400-600ms

raft结构体
先看看论文中的结构体描述:
在这里插入图片描述

type Raft struct {mu sync.Mutex // Lock to protect shared access to this peer's statepeers []*labrpc.ClientEnd // RPC end points of all peerspersister *Persister // Object to hold this peer's persisted stateme int // this peer's index into peers[]dead int32 // set by Kill()// Your data here (2A, 2B, 2C).// Look at the paper's Figure 2 for a description of what// state a Raft server must maintain.// 对照state表currentTerm int //当前看到的最新termvotedFor int //给哪个candidate投票log []Entry //log entriesgetVoteNum int //获得多少的票commitIndex int //最新的committed的log entrylastApplied int //最新的applied给state machine的log entrystate int // 当前的角色lastResetElectionTime time.Time // 上次重新选举的时间nextIndex []int // leader记录的发给每个server的next log entry的indexmatchIndex []int // leader记录的每个server复制的最高indexapplyCh chan ApplyMsg // 结构体使用chan,由leader返回给tester?//SnapShot 用到的lastSSPointIndex intlastSSPointTerm int}

详细的描述可以看注释,在parta中我们不会用到后面的这几个index和term相关的属性,都是后面几个part用到的。persister也不用管,也是后面的partd才用到(用来存快照的)
我们着重看一下这个peers
peers数组我们只需要关注它们的index,其中该raft节点本身的index就是me;然后,每个raft节点中存储的peers的index所指向的实际raft节点都是一致的(他这里没有明显指出来,但我们需要知道这一点才好理解)
然后,相比论文中的state表,我们新增了state变量表示当前该raft节点的实际状态,以及它上一次重置时间的时间戳lastResetElectionTime(用来判断是否超时)

make初始化
我们在raft.go中需要提供make接口来方面tester调用make_config构建raft集群,主要的作用就是初始化:

func Make(peers []*labrpc.ClientEnd, me int,persister *Persister, applyCh chan ApplyMsg) *Raft {rf := &Raft{}// peers对应的index就是每个server唯一的idrf.peers = peersrf.persister = persisterrf.me = me// Your initialization code here (2A, 2B, 2C).// 还是要加锁rf.mu.Lock()rf.state = FOLLOWERrf.currentTerm = 0rf.getVoteNum = 0rf.votedFor = -1rf.lastApplied = 0rf.commitIndex = 0rf.log = []Entry{}rf.log = append(rf.log, Entry{}) // 能不能删掉?rf.lastSSPointIndex = 0rf.lastSSPointTerm = 0rf.applyCh = applyChrf.mu.Unlock()// initialize from state persisted before a crashrf.readPersist(persister.ReadRaftState())// start ticker goroutine to start elections// go rf.ticker()// 定时选举candidatego rf.candidateElectionTicker()// leader定时发心跳,和log entriesgo rf.leaderAppendEntriesTicker()// 定时将committed变成applied//go rf.commitedToAppliedTicker()return rf}

很显然,前面就是各种状态的初始化。然后最后用go关键字拉起了两个协程,分别是用来控制进行定期的election选举的检查以及进行定期的leader发送心跳提醒。

changestate()函数的实现
在介绍changestate函数之前,我们先归纳一下在parta中所有状态变换的可能以及它们是否需要重置时间

  1. server x收到reqeustvote rpc, 且rpc中的term比当前的currentTerm要大,也就是说当前server x手上的信息不是最新的,那么server x -> follower(server x 之前可能是leader或者candidate哦),然后是不重置时间的(因为可能它就是一个follower)
  2. candidate自己的getvoteNum过半,选举成功,candidate -> leader, 重置时间
  3. candidate发现传rpc的term比reply的小, candidate -> follower, 不重置时间
  4. follower -> candidate 重置时间
  5. server x 给 candidate 投票后,x重置时间
  6. server x 收到leader心跳,重置时间
  7. leader看到更大的term,降级为follower, 重置时间

接下来介绍一下changestate函数

// 更换raft节点的状态,并初始化一些东西
func (rf *Raft) changeState(howtochange int, resetTime bool) {// 如果要变成followerif howtochange == TO_FOLLOWER {rf.state = FOLLOWERrf.votedFor = -1rf.getVoteNum = 0// 如果要重置TIMEOUT起始时间if resetTime {rf.lastResetElectionTime = time.Now()}}// 如果要变成candidateif howtochange == TO_CANDIDATE {rf.state = CANDIDATE// 自己给自己投票rf.votedFor = rf.merf.getVoteNum = 1// term更新rf.currentTerm += 1// 变成candidate一定会resetrf.lastResetElectionTime = time.Now()// 加入选举过程rf.candidateJoinElection()}// 如果要变成leaderif howtochange == TO_LEADER {rf.state = LEADERrf.votedFor = -1rf.getVoteNum = 0// 后续用到的nextIndex和matchIndexrf.nextIndex = make([]int, len(rf.peers))rf.matchIndex = make([]int, len(rf.peers))//重置时间rf.lastResetElectionTime = time.Now()}
}

这个函数两个参数,一个是需要变成的state,一个是是否需要重置时间。我们可以发现只有follower才可以选择是否需要变更时间,其他的candidate和leader都是变了之后时间默认重置的,然后这主要涉及到状态的再初始化,这就不需要赘述了。然后就是candidate中涉及到的一个candidateJoinElection函数,因为candidate只可能是follower变得,因此变成candidate的同时就可以发起requestvoterpc了

candidatejoinelection函数:

这里面涉及到rpc的处理我们后面再一起讲

模拟rpc
我们先看看两个调用rpc接口的函数哈(requestvoterpc和appendentriesrpc)

func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {ok := rf.peers[server].Call("Raft.RequestVote", args, reply)return ok
}func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)return ok
}

这里面都把调用rpc定义成raft绑定的方法了,这里是rf发rpc,然后peers[server]收到rpc,然后args是rf发出去的参数,通过我们编写的rpc接口处理器处理后,也就是call的第一个参数,server就会根据具体情况返回对应的reply结果,然后ok如果为true就说明网络是ok的,完成了一次有效的通信。

因此,我们为了实现rpc,要定义输入输出参数的格式,以及定义rpc处理函数;然后通过ticker来调用一个协程负责向其他节点发送rpc(这里又要拉起新的协程),然后再完成后续的判断

未完待续。。。


推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
author-avatar
晰mine
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有