lab3比lab2更加自由一点,主要是没有论文参照以及太多的资料可以查,而且调试难度也比较大。lab3A主要是实现基于Raft的kv server,client给server发送请求,然后server通过底层Raft来保持log的一致性,操作存储在内存中。lab3B主要是实现给内存快照,然后持久化到persister中。
我的代码:MIT6.824
lab3A
client
首先是实现client
的操作,我们可以先通过查看test_test.go
来了解测试过程,首先测试会新建几个server
和client
,然后调用调用client
的请求操作,client
的请求操作会通过RPC
调用server
端的实现。
主要是实现client
端的Get
和PutAppend
方法,这个其实很简单,我们只需要在两个方法中,无限循环的尝试给server
端发送RPC
请求,如果成功就返回,不成功就继续发送。
为了防止在网络不稳定等情况下,server
已经执行了操作,但是reply
会没有回到client
端,我们需要维护操作的幂等性,也就是判重。一个简单的方法是给每次client
端发送的请求都加上一个ClientId
和Seq
,其中前者是每个client
都有的唯一的64位编号,后者是随着每个新的请求递增的请求序列号。
由于我们测试的情况下,一个client
如果某个请求还没有成功执行,那么它就会一直重复当前这个请求,所以我们只需要在server
端记录一下对于每个client
,已经收到的最大的序列号是多少即可。
server
server
端主要是需要接收client
发来的请求,然后执行,执行过程主要是把log
发送到底层的Raft
服务中,然后通过Raft
来commit
这个请求,然后在server
端从channel
中获取请求,然后应用到内存中,然后通知client
端操作已经完成或者没有去完成。
整个过程是同步的,开始执行请求之后就必须等请求执行完成才能回复给client
,所以可以通过Raft
的Start
函数返回请求在log
中的index
,对于每个index
创建一个channel
来接收执行完成的消息,接收到了之后,知道是完成操作了,然后回复给client
需要单独开一个goroutine
来监视apply channel
,一旦底层的Raft commit
一个,就立马执行一个。
lab3B
snapshot
snapshot
其实并不复杂,需要做的几个点就是:
- 启动
server
的时候,需要读取snapshot
,并且要在底层的Raft
开始运行之前。 - 当
Raft state
也就是log
以及其他两个参数超过一定限度的时候,server
要将此时的内存状态保存为snapshot
- 底层的
Raft Leader
在发送心跳的时候发现和Follower
的log
不匹配的点,在它现有的log
中不存在(已经被存入snapshot
),Leader
就需要把AppendEntries RPC
变为InstallSnapshot RPC
,同时附带心跳效果。 InstallSnapshot RPC
主要是截断log
,然后其他判断和其他RPC
相同,然后往apply channel
里发送一个带snapshot
的消息,当server
端取出执行的时候,就是执行加载这个snapshot
的操作了,而不是client
的请求。