MIT 6.824 Lab2B Raft

文章详细介绍了MIT6.824课程中关于Raft算法的实验,包括前期准备、实验概述和关键函数的伪代码。实验目标是实现心跳、日志传递、同步及提交。文中提到了日志结构、广播请求、发送和处理函数的逻辑,以及算法的两个潜在缺陷和可能的解决方案。
摘要由CSDN通过智能技术生成

MIT 6.824 Lab2B Raft

一、前期准备

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

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

这篇博客也详细介绍了raft算法

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

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

在这里插入图片描述

二、实验概述

在这里插入图片描述

该部分的功能除了实现心跳之外,还得加上日志的传递,同步日志(网络故障或者crash),以及日志的提交。

主体结构在前面一节也有提到,这个实验多的一个数据结构就是日志。

type LogEntry struct {
	//日志索引
	LogIndex int
	//该命令对应的领导人任期号
	LogTerm int
	//可以执行的命令
	LogCommand interface{}
}

并且rf.log[0]我们将他视为哨兵,保存快照节点信息,目前还用不到。

三、函数伪代码

3.1 广播请求函数

func (rf *Raft) broadcastAppendEntries() {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	last := rf.getLastIndex()
	//采用批量提交  查看当前能够提交的command数量 通知线程提交  并且因为raft的一致性原则 只要有一个i 在该条件下2*num > len(rf.peers)不满足  后面的i一定也不满足 所以这里没有加break语句
	for i := rf.commitIndex + 1; i <= last; i++ {
		//判断该日志是否可以提交
	}
	// 有新日志提交   通知
	
	//发送  有日志发日志,没有日志发送心跳,我们用nextIndex[]来保存当前需要发送到当前follower中的日志下表,matchIndex[]表示该follower当前与leader同步的日志下标
	for i := range rf.peers {
		if i != rf.me && rf.state == LEADER {
				var  args AppendEntriesArgs
            	//填充数据
				go func(i int, args AppendEntriesArgs) {
					var reply AppendEntriesReply
					rf.sendAppendEntries(i, args, &reply)
				}(i, args)
			} 
	}

}

3.2 发送函数

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 {
        //如果当前状态变更  说明reply是没用的,直接返回(比如角色改变或者任期改变)
		//如果reply中发返回的任期比自己的大,说明自己已经不是leader了
        
        //返回reply.Success
        //根据返回值更新nextIndex[] matchIndex[]
	}
	return ok
}

3.3 对端处理函数

// leader节点进行日志同步  raft强制要求 leader节点 覆盖到其他server节点来保持一致性
func (rf *Raft) AppendEntries(args AppendEntriesArgs, reply *AppendEntriesReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()
	//如果该leader任期 小于当前server节点的任期,则返回当前节点最新的索引(不认可该leader)
	
	//收到leader 的心跳 重置定时器恢复正常
	rf.chanHeartbeat <- true

	//判断前一条日志是否匹配,课上老师说不匹配不能直接返回前一个,这样做效率太低会超时,这里我们直接一个for循环直接遍历,返回第一个不匹配位置。
	
	//追加日志   然会成功消息

	//因为args中还携带了leadercommindex,根据该信息,查看自己是否有command可以提交
	return
}

3.4 处理提交

本方案大部分时间都是直接使用一把大锁,但是这边把锁拆开来了,原因后面再说,baseIndex实在快照中使用的。

go func() {
		for rf.killed() == false {
			select {
			case <-rf.chanCommit:
				rf.mu.Lock()
				commitIndex := rf.commitIndex
				baseIndex := rf.log[0].LogIndex
				msgss := make([]ApplyMsg, 0)
				for i := rf.lastApplied + 1; i <= commitIndex; i++ {
					msg := ApplyMsg{CommandIndex: i, Command: rf.log[i-baseIndex].LogCommand, CommandValid: true, SnapshotValid: false, Snapshot: rf.persister.ReadSnapshot()}
					msgss = append(msgss, msg)
				}
				rf.mu.Unlock()
				for _, msg := range msgss {
					rf.chanApply <- msg
					rf.mu.Lock()
					rf.lastApplied = msg.CommandIndex
					rf.mu.Unlock()
				}
			}
	}
}()

4.算法缺陷

4.1 缺陷1:因为client提交的指令每次都是和心跳一起发送,这就导致了client提交的指令不是实时的,会造成较大的延迟。

解决方案:多开一个线程,进行指令发送

4.2 缺陷2:可能出现极端的情况,导致单向的网络出现故障,进而使得Raft系统不能工作?
Robert教授:我认为是有可能的。例如,如果当前Leader的网络单边出现故障,Leader可以发出心跳,但是又不能收到任何客户端请求。它发出的心跳被送达了,因为它的出方向网络是正常的,那么它的心跳会抑制其他服务器开始一次新的选举。但是它的入方向网络是故障的,这会阻止它接收或者执行任何客户端请求。这个场景是Raft并没有考虑的众多极端的网络故障场景之一。
我认为这个问题是可修复的。我们可以通过一个双向的心跳来解决这里的问题。在这个双向的心跳中,Leader发出心跳,但是这时Followers需要以某种形式响应这个心跳。如果Leader一段时间没有收到自己发出心跳的响应,Leader会决定卸任,这样我认为可以解决这个特定的问题和一些其他的问题。
你是对的,网络中可能发生非常奇怪的事情,而Raft协议没有考虑到这些场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值