MIT 6.824 Lab2C 2D Raft
一、前期准备
相较于前两个实验,后面两个实验主要是按照实验要求来进行。
同时还要多多注意config和test两个文件
实验地址introduction部分有一些资料可以学习一下
课堂翻译强烈推荐(里面还有相应论文),虽然可能没有上课听老师讲那么好,但是对于英语渣渣的我还是非常有用的。
代码主要分为测试部分和编写部分,可以先去大致看一下config和test文件中的内容,毕竟程序准确性依赖测试文件,同时也能知道实验各个部分的运行过程。
论文中已经把算法基本上都已经实现好了,先把需求和代码看清楚再下手(课上老师好像也这么说了)
二、结构体
type Persister struct {
mu sync.Mutex
raftstate []byte
snapshot []byte
}
type InstallSnapshotArgs struct {
Term int
LeaderId int
//快照中最后的日志条目对应的索引
LastIncludedIndex int
//快照中最后的日志条目对应的任期
LastIncludedTerm int
//快照原始数据
Data []byte
}
type InstallSnapshotReply struct {
//用于leader更新自己
Term int
}
三、实验2C
该实验需要实现的就两个函数,在课上也说明了需要保存的信息以及为什么要保存该信息,7.4 持久化(Persistence) - MIT6.824 (gitbook.io)。在make中我们执行read,在make函数中他直接为我们提供了Persist的数据,所以可以直接使用(config文件中,具体实现应该留到实验3了),之后在每个修改了log currentterm votefor数据后我们都执行persist()
func (rf *Raft) readPersist(data []byte) {
if data == nil || len(data) < 1 { // bootstrap without any state?
return
}
r := bytes.NewBuffer(data)
d := gob.NewDecoder(r)
d.Decode(&rf.currentTerm)
d.Decode(&rf.votedFor)
d.Decode(&rf.log)
}
func (rf *Raft) persist() {
// Your code here.
// Example:
w := new(bytes.Buffer)
e := gob.NewEncoder(w)
e.Encode(rf.currentTerm)
e.Encode(rf.votedFor)
e.Encode(rf.log)
data := w.Bytes()
rf.persister.Save(data,nil)
}
四、实验2D
根据实验要求,我们需要完成一个Snapshot(index int,data []byte)函数,以及同步其他节点的快照。
4.1 Snapshot(index int,data []byte)
该函数不需要我们调用,在config中,有一个实验在接收十条command后会直接运行这个函数。(对应之前把锁分开),根据题意我们只需要截断log,再修改一些数据即可。
func (rf *Raft) Snapshot(index int, snapshot []byte) {
rf.mu.Lock()
defer rf.mu.Unlock()
baseIndex := rf.log[0].LogIndex
lastIndex := rf.getLastIndex()
if index <= baseIndex || index > lastIndex {
return
}
var newLogEntries []LogEntry // log[0]是哨兵
newLogEntries = append(newLogEntries, LogEntry{LogIndex: index, LogTerm: rf.log[index-baseIndex].LogTerm})
for i := index + 1; i <= lastIndex; i++ {
newLogEntries = append(newLogEntries, rf.log[i-baseIndex])
}
rf.log = newLogEntries
rf.persist()
rf.persister.Save(nil, snapshot)
}
4.2 leader发送函数
func (rf *Raft) sendInstallSnapshot(server int, args InstallSnapshotArgs, reply *InstallSnapshotReply) bool {
ok := rf.peers[server].Call("Raft.InstallSnapshot", args, reply)
if ok {
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.state = FLLOWER
rf.votedFor = -1
return ok
}
rf.nextIndex[server] = args.LastIncludedIndex + 1
rf.matchIndex[server] = args.LastIncludedIndex
}
return ok
}
4.3 server响应函数
func (rf *Raft) InstallSnapshot(args InstallSnapshotArgs, reply *InstallSnapshotReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
//当前任期比该leader节点大 说明已经不是leader
//收到心跳,重置定时器
rf.chanHeartbeat <- true
//持久化保存leader发送的快照
rf.persister.Save(nil, args.Data)
//根据args中的信息截断当前log
msg := ApplyMsg{SnapshotValid: true, Snapshot: args.Data, SnapshotIndex: args.LastIncludedIndex, SnapshotTerm: args.LastIncludedTerm}
// 更新raft中的一些数据
rf.persist()
rf.chanApply <- msg
}
4.4 broad函数
该函数和前面函数的功能一样,负责将快照发送给其他节点,我们将它和实验2B的broad函数写在一起。
func (rf *Raft) broadcastAppendEntries() {
// 前面一样省略
for i := range rf.peers {
if i != rf.me && rf.state == LEADER {
if rf.nextIndex[i] > baseIndex { //baseIndex代表上一次快照的最后一个logindex
//发送日志
} else {
//发送快照
}
}
}
}
五、总结
实验2比实验1难好多,实验2中有好多细节,论文中的内容和课上的内容都要搞清楚在开始动笔写,不然可能会被推到重写(很痛苦)。
调试全程都是用的fmt,前面还想搞远程调试(浪费了好多时间,最后也没搞好),多线程也不好打断点调试。