MIT 6.284 Lab2A Raft

文章介绍了MIT6.284课程实验中关于Raft共识算法的实现,包括实验准备、选举过程、数据结构定义和具体函数的伪代码。讨论了Go语言中利用channel和协程简化代码结构的优势,并提出了算法的潜在问题,如在网络故障时可能导致的额外选举及其解决方案。
摘要由CSDN通过智能技术生成

MIT 6.284 Lab2A Raft

一、前期准备

实验地址introduction部分有一些资料可以学习一下

课堂翻译强烈推荐(里面还有相应论文),虽然可能没有上课听老师讲那么好,但是对于英语渣渣的我还是非常有用的。

代码主要分为测试部分和编写部分,可以先去大致看一下config和test文件中的内容,毕竟程序准确性依赖测试文件,同时也能知道实验各个部分的运行过程。

论文中已经把算法基本上都已经实现好了,先把需求和代码看清楚再下手(课上老师好像也这么说了)

在这里插入图片描述

二、实验要求

该实验主要是让我们进行leader的选举,测试文件有三个,一个是初始化后的选举,一个是当网络出现异常时选举,以及多次迭代,随机断开网络连接选举。

根据实验要求,我们需要实现的函数功能有leader选举,leader定时发送心跳,以及角色转换。

在这里插入图片描述

三、思考

因为在之前还没接触过go,C++用的比较多,所以先想了一下如果用C++实现该如何(多机),因为之前看过muduo网络编程,将行为都定义为事件,之后统一epoll触发,调用回调函数。

但go中有channel和协程导致结构可以很简单,根据上图我们也可以根据状态自动机的思想来简化代码结构。

for rf.killed() == false {
			switch rf.state {
			case FLLOWER:
				select {
				case <-心跳:
				case <-投票:
				case <-超时:
				}
			case LEADER:
				广播心跳
				time.Sleep(HBINTERVAL)
			case CANDIDATE:
				选举前处理
				广播给其他节点 使其为自己投票
				select {
				case <-选举超时:
				case <-收到心跳:
				case <-选举成功:
				}
			}
		}

根据选举的行为和上图的角色转换,该实验的整体代码就够差不多就这样。
选举原则:

  • 每个节点,对每个任期(term)只能投出一张票.
  • 收到选举请求后,如果该请求的事务日志编号小于自己的机器,那么就不会投票.
  • 保证新的leader拥有更新的数据 收到选举请求后,如果该选举编号(term)小于自己的term,那么就不会投票.
  • 当一台机器收到多数投票,代表选举成功成为新的leader.
四、数据结构定义

数据结构在论文中都有定义,可以直接照抄,同时论文中还提到可以将心跳和提交信息一起发。(当然当前实验还用不到,在实验2B中需要)

type Raft struct {
	mu        sync.Mutex          // Lock to protect shared access to this peer's state
	peers     []*labrpc.ClientEnd // RPC end points of all peers
	persister *Persister          // Object to hold this peer's persisted state
	me        int                 // this peer's index into peers[]
	dead      int32               // set by Kill()

	// Your data here.
	// Look at the paper's Figure 2 for a description of what
	// state a Raft server must maintain.

	//channel
	state         int
	voteCount     int
	chanCommit    chan bool
	chanHeartbeat chan bool
	chanGrantVote chan bool
	chanLeader    chan bool
	chanApply     chan ApplyMsg

	//persistent state on all server
	currentTerm int
	votedFor    int
	log         []LogEntry

	//volatile state on all servers
	commitIndex int
	lastApplied int

	//对于每一个服务器,记录需要发给它的下一个日志条目的索引(初始化为领导人上一条日志条目的索引值 + 1)能够起到同步作用(例如跟随者故障之后 恢复)
	nextIndex []int
	//对于每一个服务器,记录已经复制到该服务器的日志的最高索引值(从0递增)
	matchIndex []int
}
type RequestVoteArgs struct {
	//任期
	Term int
	//全局唯一候选者ID
	CandidateId int
	//候选人最新日志条目对应的任期号
	LastLogTerm int
	//候选人最新日志条目的索引值
	LastLogIndex int
}
type RequestVoteReply struct {
	//表示确认的server的任期 如果candidate的term小于它。则更新
	Term int
	//表明该follower是否给自己投票
	VoteGranted bool
}
type AppendEntriesArgs struct {
	//该term为leader的当前任期号
	Term int
	//全局leader唯一id
	LeaderId int
	//最新日志之前的日志的leader任期号
	PrevLogTerm int
	//最新的日志之前的索引值
	PrevLogIndex int
	//将要存储的日志条目
	Entries []LogEntry
	//leader提交的日志条目索引值
	LeaderCommit int
}
type AppendEntriesReply struct {
	//收到所有server节点的rpc回复term
	Term int
	//是否认可当前leader身份 继续为主节点
	Success   bool
	NextIndex int
}
五、具体函数伪代码

5.1 选举

选举时无需所有follower都返回结果,只要有超过半数投出了赞同票即可。

func (rf *Raft) sendRequestVote(server int, args RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if ok {
		rf状态变化。。
        follower投自己一票。。
	}
	return ok
}
func (rf *Raft) RequestVote(args RequestVoteArgs, reply *RequestVoteReply) {
    rf.mu.Lock()
    defer rf.mu.Unlock()
    defer rf.persist()
    处理term不满足要求情况
    只有候选人满足投票条件才能投票(任期比自己高或者等于自己,同时拥有最新的日志)
}
func (rf *Raft) broadcastRequestVote() {
	var args RequestVoteArgs
	填充args数据
	for i := range rf.peers {
		if i != rf.me && rf.state == CANDIDATE {
			//并行的推送给所有节点进行选举leader
			go func(i int) {
				var reply RequestVoteReply
				rf.sendRequestVote(i, args, &reply)
			}(i)
		}
	}
}

5.2 心跳

在该实验中可以将发送心跳和日志放在一起写所以该部分放到下一篇。

type LogEntry struct {
	//日志索引
	LogIndex int
	//该命令对应的领导人任期号
	LogTerm int
	//可以执行的命令
	LogCommand interface{}
}
func (rf *Raft) broadcastAppendEntries() // 和上一个差不多
func (rf *Raft) sendAppendEntries(server int, args AppendEntriesArgs, reply *AppendEntriesReply) bool {
	ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if ok {
		if rf.state != LEADER {
			return ok
		}
		if args.Term != rf.currentTerm {
			return ok
		}
		2B的内容
	}
	return ok
}
func (rf *Raft) AppendEntries(args AppendEntriesArgs, reply *AppendEntriesReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()

	 //  具体留到2B再写
}
六、算法缺陷

6.1 缺陷1:假设有一个Leader l1,和两个follower f1,f2,当f2发生网络故障时,会因为收不到l1的心跳,导致f2的term一直增加,当f2网络恢复正常时,因为他的term是最大的,会触发一次选举,更新其他节点的term,但由于f2的日志不是最新的,所以也不会选举成功。

解决方法:PreVote,Candidate首先要确认自己能赢得集群中大多数节点的投票,这样才会把自己的term增加,然后发起真正的投票。

赢得投票条件为:

  1. 没有收到有效领导的心跳,至少有一次选举超时。
  2. Candidate的日志足够新
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值