MIT 6.824 Lab2 Raft (4)

写在前面,part D快照这部分没有完全通过,看debug 日志十分痛苦,所以就用了别人的raft直接快进到 lab3。如果以后有时间了再重新来看吧,目前这篇只是想记录下,part D最基本的测试点怎么过,以及理解part D中多出来的几个函数调用之间的关系。

我自己的partD实现在这里,不过有bug。别人的代码就暂时不贴了,未经别人同意。

函数之间的关系

课程给出了两个基本的函数,分别是Snapshot(),CondInstallSnapshot()这俩,当然了自己还需要根据论文快照那一节实现InstallSnapshot。一开始的时候不能很好的理解这几个函数,所以下面说一下我自己的理解。

论文中提到使用快照来避免日志的无限制增长,最开始我误认为生成快照的工作是由Raft来做的,而实际上生成快照的工作是由基于Raft的应用来做的。比如说一个KV 数据库,它记录下 日志: put(1,13)。然后由Raft将这条日志同步给集群中的其他节点,当日志增长到一定大小的时候,就要对日志截断,释放一些空间出来。关键点就是要考虑到Raft只是一个一致性协议,它就是保证上层应用的数据能够很好的同步给集群中的其他节点

认识到这一点之后,就很好理解这几个函数的作用了

Snapshot(index int, snapshot []byte):

应用程序将index(包括)之前的所有日志都打包为了快照,即参数snapshot [] byte。那么对于Raft要做的就是,将打包为快照的日志直接删除,并且要将快照保存起来,因为将来可能会发现某些节点大幅度落后于leader的日志,那么leader就直接发送快照给它,让他的日志“跟上来”。 不过,并不是所有的快照接收到就必定应用到上层程序当中,比如说接收到快照里面包含着:put(1,14) ,put(2,14) ,我们要判断快照里面哪些日志是可用的,有效的才将他们应用到程序当中。这就是CondInstallSnapshot()的工作。

CondInstallSnapshot(lastIncludedTerm int, lastIncludedIndex int, snapshot []byte) bool:

如果一个节点接收到一个快照后,提交给上层应用(由InstallSnapshot来完成这个工作),上层应用必须要判断,目前接收到的快照是不是有效的,如果快照的lastIncludedIndex 比自己的最后一个日志的log entry index要大,那也就是说自己的日志太落后,要更新下。如果收到老的快照,那么就直接丢弃。不过我目前在这里的实现还有些问题,后续在修改这里的描述吧。

InstallSnapshot

这是一个RPC处理函数,每一个节点在这个处理函数中接收来自leader的快照,然后要判断一下是不是过期的快照。不然的话就直接传给上层应用,让它去判断是否要应用快照里面的内容。 在lab中,往applyCh写入一个snapshot就是将快照传给应用的过程

代码的实现

总的来说,实现快照以后,就是要调整一些对于日志的log index的调整。这个稍后再说,先说一下basic测试点。

basic要做的东西比较简单,就是丢弃index之前的所有日志,并且将snapshot保存下来。不过这里对于日志的删除并不是简单的slice操作,因为slice操作不利于GC对于数组的回收,所以我们在删除日志的时候,要copy一下。提示中也说到了这一点:

Raft must discard old log entries in a way that allows the Go garbage collector to free and re-use the memory; this requires that there be no reachable references (pointers) to the discarded log entries.

代码如下:

func (rf *Raft) Snapshot(index int, snapshot []byte) {
    // Your code here (2D).
    DPrintf("{Node %v} wants to snapshot at index %d",rf.me,index)
    firstIndex := rf.getFirstEntry().Index
    if index <= firstIndex {
        DPrintf("{Node %v} cannot make snapshot at %d index",rf.me,index)
        return
    }
    rf.deleteObsoleteLog(index)
    rf.persister.SaveStateAndSnapshot(rf.encodeState(),snapshot)
}
// 如果此时的firstEntry的index = 10,lastEntry = 14
// 新的数组长度就应该是logLen := rf.getLastEntry().Index - index + 1,
// 后面要+1的目的是,注意raft的log[0]是一个nil的日志
// 所以在上面的例子中,新的log长度就是 14 - 10 + 1 = 5
func (rf *Raft) deleteObsoleteLog(index int) {
    logLen := rf.getLastEntry().Index - index + 1
    newLog := make([]Entry,logLen)
    copy(newLog,rf.log[index - rf.getFirstEntry().Index:])
    rf.log = newLog
    rf.log[0].Command = nil
}

这样一来,还有别的地方要修改下标,比如说在同步日志的时候,rf.log[prevLogIndex].Term已经不适用了,比如说prevLogIndex = 10,但是实际上len(rf.log) = 3

就会发生数组越界,所以这里应该是rf.log[args.PrevLogIndex - rf.getFirstEntry().Index].Term

还有一些地方也要做类似的修改。具体就看代码中的实现吧。

待续

目前就做完了basic部分,后面的debug比较困难,有时候日志打的很长才能看到问题。有时间再补。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇