热门标签 | 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(这里又要拉起新的协程),然后再完成后续的判断

未完待续。。。


推荐阅读
  • poj 3352 Road Construction ... [详细]
  • 本文介绍如何使用线段树解决洛谷 P1531 我讨厌它问题,重点在于单点更新和区间查询最大值。 ... [详细]
  • T15483.【清华集训2017模拟11.26】简单路径T25484.【清华集训2017模拟11.26】快乐树T35485.【清华集训2017模拟11.26】字符串T1结论题,结论很 ... [详细]
  • 本文介绍了几种常用的图像相似度对比方法,包括直方图方法、图像模板匹配、PSNR峰值信噪比、SSIM结构相似性和感知哈希算法。每种方法都有其优缺点,适用于不同的应用场景。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • 如何在Linux服务器上配置MySQL和Tomcat的开机自动启动
    在Linux服务器上部署Web项目时,通常需要确保MySQL和Tomcat服务能够随系统启动而自动运行。本文将详细介绍如何在Linux环境中配置MySQL和Tomcat的开机自启动,以确保服务的稳定性和可靠性。通过合理的配置,可以有效避免因服务未启动而导致的项目故障。 ... [详细]
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文详细介绍了如何使用Python中的smtplib库来发送带有附件的邮件,并提供了完整的代码示例。作者:多测师_王sir,时间:2020年5月20日 17:24,微信:15367499889,公司:上海多测师信息有限公司。 ... [详细]
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社区 版权所有