MIT 6.824-lab2 Raft

这个lab2属实麻烦,花了两天时间写代码,最后花了四天时间来debug才通过,debug的手段也很有限,基本只能通过看日志来发现代码中的逻辑问题,最终回顾写出来的代码还乱得不行,先做个简单的记录,以后如果还有时间来重构一下代码。

1.Raft

raft算法是一种分布式共识算法,相对于paxos算法而言更加简单容易理解(看起来简单,实现起来也挺不容易的)。raft的论文很多,也有很多中文版的,看一看很容易理解。
raft中文版

2. 实验要求

本次实验分为A、B、C三个部分,将三个任务都实现就是一个完整的raft协议。

  • 2A:完成raft的选主功能,且主节点宕机后能够重新选主
  • 2B:完成raft的日志复制功能,且为选主功能加上限制条件(节点只为日志比自己更新的候选者投票),且能够在各种条件下(如网络波动)的条件下使节点一致
  • 2C:增加日志持久化功能,某些节点宕机重启后也能让整个集群保持一致

3. 分析

对于raft状态机中需要的数据,各种rpc中需要携带哪些信息,这些内容在paper中都详细的标注了,照着论文实现就OK,我简单的讲一下我觉得实现中遇到的困难:

3.1 任务拆分

raft中每个节点需要承担多种不同的功能,处理/发送心跳、投票,添加日志,apply日志,状态转变等。将这些任务拆分到不同的协程中去执行,会更加的简单。

  • RPC:该协程负责处理其他节点的投票请求和日志添加请求并进行回复
  • Timer:定时器协程,协程中维护一个定时,当一定时间未收到主节点的心跳时,将自身变为候选人,发起投票请求
  • Heartbeat:心跳协程,主节点才会使用,定期向其他节点发起心跳
  • BackWork:后台协程,负责将已经commit的log进行apply,本身跟raft协议是没有关系的
  • AppendLog:日志添加协程,主节点才会使用,通过该协程将主从之间尚未match的log发送给从节点
  • PrintLog:打印当前节点状态的协程,用来debug的,如果没有也不影响实验结果。
3.2 数据与状态

对于临界数据一定要加锁,除此之外,还需要注意状态的判断,比如节点A在候选人状态时发起了投票,节点A在等待投票结果,但是因为网络波动,投票的回复信息晚了几秒钟,在这期间内,节点B已经成为新的leader并向A发送了心跳,A此时已经变成了follower节点,这次A之前发起的投票信息回复了,票数过半,但是其实已经是失效信息了,A需要判定自己当前的状态和term,最终判断自己不能成为leader,而不是只根据过半投票数让自己成为leader节点。

3.3 异步RPC

节点中的RPC应该都是用异步的,即另开一个协程来进行RPC。如果直接在当前协程中进行同步RPC,等待RPC的回复,当网络波动,数据延迟时,当前协程就被阻塞了,影响整个raft状态机的运转。

3.4 一点优化

在论文的推荐实现中,对于日志添加请求的回复中只携带两个数据,一个是本次添加是否成功,一个是回复节点当前的term。有这两个数据本身也能够保证raft的集群的安全性,但是可能造成效率低下。当主节点收到回复时,如果添加成功,那就会调整matchIndex和nextIndex,然后发送后续该发送的log,如果回复添加失败,主节点会知道是因为发送的log太过于超前了,主节点会将nextIndex-1,然后重新进行发送。这样做的问题是,如果主从log差了几百条,那么就需要几百次的添加请求失败,主节点才能将从节点真正需要的log发送过去。

在2C的一个实验(TestFigure8Unreliable)中,主从之前的log差距较大,且网络波动,节点之间没有办法顺利的通信几百次以同步双方的log。

这时候就应该在日志添加的回复请求中多增加一个index,告知主节点,你下次应该从这个index号的log开始发送,我必能够接收(我在代码中将index设置为了从节点的commitIndex),从而减少主从之间的网络通信次数。(作废的方法,这种方法也能达到同步,但是实际工程中应该无法使用,因为可能会导致大量重复log的复制)

之前按照自己的思路实现如上所写,后来看了6.824的视频,在课程中针对性的讲了这个问题,给出的思路如下:

加速同步的优化
从机对于AppendEntry的回复加上三个值:

XTerm//冲突log的term
XIndex//XTerm的第一条log的index
XLen//log的长度

主表示主机,后续的的数字表示log对应的term
主:4 6 6 6,表示主机有四条log,第一条log来自term=4,第二条来自term=6,第三条第四条皆来自于term=6

  • CASE1:
    从:4 5 5
    主:4 6 6 6
    主机发了4(6)match失败,从机回复了Xterm(5),Xindex(2),Xlen(3),由于主机中没有term=5,直接从Xindex开始重新同步,将nextIndex置为Xindex(2)

  • CASE2:
    从:4 4 4
    主:4 6 6 6
    AE失败后从机回复了Xterm(4),Xindex(1),Xlen(3),因为主机中有term(4),直接从term4的最后一条log开始同步,将nextIndex=1

  • CASE3:
    从:4
    主:4 6 6 6
    从机回复Xterm=-1,表示我根本没有这条log,那么主机直接将nextIndex调整为Xlen+1

4.实现

完整代码点这点

最终连续通过测试一百次。
测试脚本如下:

for ((a=1;a<=100;a++))
do
	echo ".....index =$a"
	go test > ./test/report_$a
	s=$(tail -n 1 ./test/report_$a)
	#echo ${s:0:2}
	#echo "ok"
	if [ ${s:0:2} == 'ok' ]
	then
		echo "test $a Passed"
		rm ./test/report_$a
	else
		echo "test $a Failed"
	fi
done

你可能感兴趣的:(6.824,go,raft算法,分布式,mit,分布式一致性协议)