fravenx / MIT-6.824

Lab of MIT 6.824 2023 所有lab 稳定通过一万次以上 All labs stably passed 10,000 times

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

实验结果

lab1(10次)

2A(2000次0fail)

2B(10000次0fail)

2C(10000次0fail)

2D(10000次0fail)

3A(500次0fail)

3B(500次0fail)

4A(500次0fail)

4B + 2Challenges(500次0fail)

过程中踩的坑/难点(除了严格遵守论文图2)

lab1

  1. 我的临时文件最开始是放在src/mr目录下,在使用main程序时运行代码时当前目录为"src/main",程序能够正常运行。但在使用测试文件时,运行过程 中当前目录 为"src/main/mr-tmp",会出现找不到目录的错误。
  2. 在将调用mapf将所有收集到的中间值保存到文件时,可以先在内存中创建nreduce个桶,然后再将这些值先映射入桶中,最后再将这些值存入中间文件。

2A

  1. 什么时候重置选举的计时器?

    (1) 选举计时器时间到时,节点状态变为candidate,重置计时器

    (2) 节点收到RequestVote的RPC请求时,若给予投票,则重置计时器

    (3) 节点收到leader有效的heartbeat请求时

  2. 并发控制,对于互斥锁如何设计?

    (1) 处理rpc方法时,可在一开始直接尝试换取锁,并紧接着使用defer释放锁

    (2) 在调用rpc之前,要先释放锁,不然调用rpc请求可能会有较长延时,长时间占用锁不释放掉可不太好

  3. 在该领导选举计时器方法中,若节点的状态为leader要退出时,记得用break语句而不是return语句,否则该节点的领导选举计时器会失效,且之后不再尝试成为leader🙂

    截屏2023-09-07 18.34.58
  4. 节点收到发送的rpc请求回复时,要对比该请求发送时的args.Term是否和节点此时的Term相同,若不相同,该回复已过期。

2B

  1. 节点在收到AppendEntry的RPC请求时,要对prevLogIndex和prevLogTerm进行检查,若不匹配,可以优化leader的nextIndex回退的方式。对此,我采取的优化策略是在RPC回复中增加一个FirstIndex的字段。

    (1) 若该follower节点中并不存在prevLogIndex对应的Entry,则置该FirstIndex = -1,leader收到回复后,将该follower对应的nextIndex置为prevLogTerm第一次出现的位置

    (2) 若该follower节点中存在prevLogIndex对应的Entry,但该Entry.Term != prevLogTerm,则在FirstIndex填入follower的log中这个冲突Entry的Term第一次出现的位置,leader收到回复后,置该follower的nextIndex为firstIndex

  2. leader只会commit和自己的currentTerm相同term的Entry。因此,我的具体做法是在leader发送AppendEntry的RPC请求时,检查log[]中最后一条entry.term == rf.currentTerm为真时,才会给args.Entries[]字段添加leader中要尝试commit的Entry

  3. leader发送AppendEntry的RPC请求收到reply.Success为真后,在更新matchIndex不能直接设置为Len(log[]),而是prevLogIndex + len(entries[]),因为在发送rpc请求后,leader中的log[]长度可能会改变

2C

  1. 2C就没什么好说的了,如果2A和2B设计的比较周到的话,2C就只需要在每次需要持久化的状态改变时调用下persist()函数就行,只不过2C的测试案例相比之前更加苛刻,增加了网络不稳点的情况。我运行10000次出现25次fail,错误内容为apply out of order,查看日志定位出问题在apply实现上,之前apply()都是在commitIndex更新后进行同步调用,违背了只能用一个实例来进行apply操作的原则,修改实现为节点启动后创建一个新的协程applier只用来进行apply操作,使用sync.Cond来同步,在lastApplied < commitIndex时用cond.Wait()来等待。

2D

  1. 增加的InstallSnapshotRpc应该如何与图2交互?我的思考结果:

    (1) 在Snapshot(Index int, snapshot []byte)中,不必完全按照论文要求截断index之前的所有log(包括index),我的实现是截断index之前所有log(不包括index),这样明显实现更加简单,不然以前的一些代码要重新设计(AppendEntries的RPC请求对比上一条entry检查机制),边界情况也会增多。

    (2) 什么时候发送InstallSnapshotRpc请求?在leader发送AppendEntriesRpc时出现nextIndex[i] <= rf.lastIncludedIndex情况时,因为nextIndex[i]是节点S[i]期待收到的下条命令乐观估计,rf.lastIncludedIndex是leader已经apply且其之前的log都已被删掉。

    (3) 节点在收到InstallSnapshot的Rpc请求时,若args.Term < rf.currentTerm || args.LastIncludedIndex <= rf.commitIndex则该请求过期,直接丢弃。清空自己的log,写入一条index为args.LastIncludedIndex,term为args.LastIncludedTerm的Entry,command可以不填(这条entry只用于之后的日志添加一致性检查),并置lastapplied=commitIndex = args.LastIncludedIndex,向applych发送snapshot,以及持久化当前最新snapshot和raft_state

    (4) leader收到InstallSnapshot请求回复时,在确保rpc发送期间leader状态未改变以及该回复未过期后,更新该节点的nextIndex和matchIndex分别为args.LastIncludedIndex + 1和args.LastIncludedIndex。

3A

  1. 在server的RPC处理中,为了等待从appier中出来的命令响应Clerk而设置的chan,一定不要使用默认的无缓冲的chan,而要设置一个缓冲空间为1(一个client只会有一个命令at the moment),由于默认的chan是同步的,server中RPC请求和appier两者阻塞,系统会超时,命令一直无法完成

  2. client除了在Err为ErrWrongLeader需要更换leader,在调用RPC请求ok为false时也要更换leader,否则系统会超时,一直无响应。

  3. client的clientId和seqno要写入Op结构体中,此操作是为了让follower的table能够同步更新,从而保证请求只会被执行一次。

3B

  1. raftState大于阈值时持久化server的data和记录客户端最新序列号的table,以及sever启动时读取已保存的data和table。

  2. 增加follower能够快速catch up的机制:

    1)在appendentrie的RPC请求中,若回复为false(preEntry检测策略失败),立即向该follower再次发送appendentries的RPC请求,而不要等到下一次心跳时间

    2)若leader更新了commitIndex,立即向其余follower发送心跳,以便follower也能够更新commitIndex

    3)在leader发送快照的请求中,此follower可能已经落后较多条日志,在发送完日志,收到肯定回复后,立即再次向该节点发送心跳。

    否则lab2能够稳定通过,而3B会超时。

4A

  1. Rebalance函数的设计:

    用一个map记录gid到shard的映射,统计map中每个gid对应的shard数组的最大值和最小值,只要相差大于1时,不断移动最大shard数组中部分元素到最小shard数组中,并更新config.Shards,当gid为0的shard数组不为空时,优先清空gid为0的shard数组(gid非0)

4B + 2challenges

  1. 如何更新配置:

    单独开一个协程,不断轮训shardctrler节点最新配置,写入日志,等配置从applych接收到后,保证集群组中的配置同步更新。为了保证配置one by one更新,只有leader节点的每个shard都处于正常状态时才会拉取配置。

  2. 节点维护上一个配置,当前配置和每个shard的状态,在持久化时写入

  3. 维护每个shard的三个状态:

    1)Working 正常状态

    2)Missing 上一配置有此shard,当前配置没有

    3)Adding 上一配置没有此shard,当前配置有

    节点收到client请求时,判断key对应的shard状态是否正常,若shard不是Working状态,则拒绝执行

  4. 单独开一个协程,leader不断轮训是否有shard需要转发,由leader发送PushShardRPC,其中args包括给新集群的data,节点自身的判重数组,发送给该gid的shards数组,目标集群收到该rpc请求后,由leader负责写入日志,在applych中收到后,置相关shards为正常状态,更新判重数组table和数据data,还要保证由配置变更由集群a发送给集群b的PushShardRPC只会被执行一次,具体方法是若rpc的Num与目标集群节点配置Num相等且leader相关shard状态为不可用时,则该RPC还未被执行过,写入日志,否则不再执行。当PushShardRPC请求完成后,为了清除源集群中不必要的shards,目标集群发送DeleteShardRPC,以便源集群进行垃圾回收

  5. 源集群收到DeleteShardRPC后,由leader写入日志,待从applych收到后,清除属于不必要shard的键值对,并置相关shard状态为正常,此RPC只要对比Num即可,不必担心被多次执行的问题,如果想要判重,方法与PushShardRPC相似。

  6. 注意写入日志中结构体的设计,不要有多余的无关变量(比如DeleteShardRPC中可以包含shard相关的key数组,而不要存shard相关的key,value键值对),否则challenge1会无法通过。

About

Lab of MIT 6.824 2023 所有lab 稳定通过一万次以上 All labs stably passed 10,000 times


Languages

Language:Go 89.7%Language:Python 6.4%Language:Shell 3.5%Language:Makefile 0.4%