6.824 Lab3

Task

在 raft 框架的基础上建立一个容错的 KV 数据库。重点在于理解 Service 层和 Raft 层的交互,代码方面较为简单,故不进行赘述。

Step

  1. 在 client 和 service 两层完成 Get(), Append(), Put()
  2. 对重复的 Append 或 Put 进行去重
  3. service 层的快照与持久化(快照存储的是 Key/Value 等元数据) lab3

tips:

值得一提的是在写完 lab3 的大概框架后,TestSpeed3A 一直不通过,因为要求是平均 33ms 一个 commit,但正常来说 commit 时间应该和 heartbeat timeout 差不多,遂修改 Start() 函数,在 command 被 Service 送来的时候发起一次心跳,进行日志同步。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func (rf *Raft) Start(command interface{}) (int, int, bool) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.state != Leader {
		return NULL, NULL, false
	}
	index, term := rf.getLastLogL().Index+1, rf.currentTerm
	rf.log = append(rf.log, Entry{
		Command: command,
		Term:    term,
		Index:   index,
	})
	DPrintf(dInfo, "S%v <- %vst command %v", rf.me, index, command)
	rf.persist()
	rf.startHeartbeatL(false)
	rf.heartbeatTimer.Reset(getHeartbeatDuration())
	return index, term, true
}

我们还可以进行一点优化:当 Leader 发送心跳时,加上一个 bool,表明 Leader 真的只是发送心跳,或者是为了快速同步日志,如果是后者,还需要判断每一个 Follower 的 nextIndex 是否需要同步,如果不满足 (rf.getLastLogL().Index > rf.nextIndex[i]),则不再同步日志。

正常来说到这一步就没问题了,但是我又发现了 lab2 的一个遗留 bug,当 commit 频率变得很快时 Leader 就会暴毙(之前的测试仅仅通过 heartbeat timeout 同步日志,频率较低,基本没有出现这个问题)。通过打 log,发现 commit 是通过单独的 go routine 接收 channel 的信号实现的,因为 channel 初始化为make(chan int,1),在 Leader 连续 commit 时容易堵塞,其实理论上会缓慢通过,但还是暴毙了,属于是一个很玄学的 bug。最后将 channel 扩容成 100 就轻松过了:)

updatedupdated2024-04-302024-04-30