MIT6.824 spring21 Lab3A总结记录
写在前面
lab3A也完成了,前前后后也改了几版才改成现在的样子。
老规矩,记录一下设计思路。
代码见:https://github.com/sun-lingyu/MIT6.824-spring21/tree/KV-3A
代码结构
由于client的处理逻辑很简单,这里略去。
整体逻辑如上图。(图中的hreply是一个struct;h表示handler)
使用一个专门的applyListener goroutine监听applyCh。所有对KV状态的修改均由applyListener 进行。
当leader接受到RPC请求时:RPC Handler会先执行rf.start,将请求放入raft log。然后RPC Handler将等待raft达成共识,等待applyListener完成相应的状态修改/查询,然后返回reply。
图中没有画出来的:leader需要维护两个map:
1.pendingChannels:以log index为key,以hreply channel为value。每一个在leader处,已经执行了start,并且正在等待raft达成共识的RPC handler,都有一个与start返回的index相对应的channel。这是为了applyListener能够与RPC handler通信。
2. pendingMap:以log index为key,以Op为value。每一个在leader处,已经执行了start,并且正在等待raft达成共识的RPC handler,都有一个与start返回的index相对应的Op。这是为了applyListener能够进行duplicate detection。
当applyListener从applyCh收到了一个Op,它首先执行duplicate detection。
接下来,若这个Op不重复,且是PutAppend,则修改KV状态。
接下来,若它发现:这个Op的log index在其维护的pendingChannels和pendingMap中有相应的value,这就代表有相应的RPC handler在等待。此时applyListener首先对比:pendingMap中的Op和从applyCh接收的Op是否相同。
若不相同,则表示有新的leader在这个log index处commit了新的entry。这表明本机已经不再是leader。应该遍历所有的pendingChannels键值对,告知所有等待的RPC Handler这个消息(即:向所有等待的RPC Handler发送ErrWrongLeader)。
若相同,则applyListener向对应的RPC Handler发送OK。若对应的RPC是Get,还要同时发送查询到的value值。
上面描述的处理逻辑还不完整。我将在下面的两个部分中加以补全。
Duplicate Detection
如何发现重复的PutAppend请求?
我的实现方式是:每一个client都维护两个值:
1. 一个独特的ID(用递增的int64表示)
2. 一个version number。
每当client要执行一个新的PutAppend请求,该client会将其version number增加1。当client向server发送PutAppend调用时,附上version number。
这样,同一个client的重复请求就可以通过ID+version number加以识别。
在server端,也维护一个以client ID为key,以该client的version number为value的map。每当server成功执行PutAppend请求,都将map中相应ID的version number增加1.
这样,server就可以通过version number来判断client的请求是否已经成功执行。
如果server遇到了已经成功执行过的PutAppend请求,它将直接返回OK。
New Leader Detection
在做这次实验的时候,我的实现在
partitions, one client (3A)
这个测试用例中总是卡住,不能继续向下执行。
在经过各种尝试,都没有效果之后,决定看看别人的博客。
最后终于在https://zhuanlan.zhihu.com/p/130671334 这里找到了问题所在😂
卡住的原因如下:
在我上面描述的实现中,只有当applyListener从applyCh中收到了新的Op,它才能够发现自己已经不再是leader。
若新leader没有commit任何新entry,那么旧leader的KV server将永远不会发现:它已经不再是leader。
因此系统将卡住,无法继续。
为了修复这个问题,我采用了与上面知乎专栏中一样的方法:
修改raft leader,使其在刚刚选举成功时向applyCh发送一条特殊的消息。上层service收到这条消息后,将执行一次rf.Start,在raft log中放置一条特殊的命令。随着这条命令的commit,所有的KV server都将发现有新的server选举成功。
这种实现较为简单,但是其修改了下层raft,我觉得不够完美,但是可以接受。
写在最后
Lab3A比较简单。但是仍然需要精心设计来保证其正常工作。
New leader detection是一个比较难的点,需要额外注意。我主要的时间都花在了这里。