实验内容
使用 lab2 中的 Raft 库构建 Fault-tolerant K/V Service,即维护一个简单的键/值对数据库,其中键和值都是字符串。具体来说,该服务是一个复制状态机,由多个使用 Raft 进行复制的键/值服务器组成,只要大多数服务器处于活动状态并且可以通信,该服务就应该继续处理客户端请求。
实验环境
OS:WSL-Ubuntu-18.04
golang:go1.17.6 linux/amd64
Part B: Key/value service with snapshots
修改 KVServer 与 raft 合作,以节省日志空间,并使用 Lab 2d 的 raft 的 Snapshot() 方法减少重新启动时间。
测试时会给 StartKVServer() 传递 maxraftstate 参数,它表明持久化的 Raft 状态的最大以字节数(包括日志,但不包括快照);因此若调用 persister.RaftStateSize() 后发现当前持久化状态大小接近阈值 maxraftstate 的话,就应该调用 Raft 的 Snapshot() 方法来保存快照;若 maxraftstate 为 -1,表明不必创建快照。
所有 kvserver 收到 ApplyMsg 都会执行相应的命令,其中 kvserver leader 将执行结果通知 waitCh;kvserver follower 会收到由 leader 发来的快照更新自身状态。
除此以外,所有 kvserver 收到 ApplyMsg 执行相应的命令后(命令被过半数 raft 提交),应该检查自身持久化状态大小,并根据上述条件及时保存快照。
读取持久化状态
快照内容:kvs 和 lastReq
func (kv *KVServer) readPersist(data []byte) {
if data == nil || len(data) < 1 { // bootstrap without any state?
return
}
r := bytes.NewBuffer(data)
d := labgob.NewDecoder(r)
var kvs map[string]string
var lastReq map[int64]int64
if d.Decode(&kvs) != nil ||
d.Decode(&lastReq) != nil {
log.Println("decode fail")
} else {
kv.kvs = kvs
kv.lastReq = lastReq
log.Println("restore success")
}
}
接收 applyCh 中的快照信息
func (kv *KVServer) apply() {
for kv.killed() == false {
applyMsg := <-kv.applyCh
kv.mu.Lock()
if applyMsg.CommandValid {
if applyMsg.CommandIndex <= kv.lastIncludedIndex {
kv.mu.Unlock()
continue
}
if op, ok := applyMsg.Command.(Op); ok {
log.Println("exec command")
kv.exec(&op)
term, isLeader := kv.rf.GetState()
if isLeader && applyMsg.CommandTerm == term {
if waitCh, ok := kv.waitCh[applyMsg.CommandIndex]; ok {
waitCh <- &op
}
}
if kv.maxraftstate != -1 && kv.persister.RaftStateSize() >= kv.maxraftstate {
w := new(bytes.Buffer)
e := labgob.NewEncoder(w)
e.Encode(kv.kvs)
e.Encode(kv.lastReq)
kvstate := w.Bytes()
kv.rf.Snapshot(applyMsg.CommandIndex, kvstate)
}
kv.lastIncludedIndex = applyMsg.CommandIndex
}
} else if applyMsg.SnapshotValid {
if applyMsg.SnapshotIndex <= kv.lastIncludedIndex {
kv.mu.Unlock()
continue
}
kv.readPersist(applyMsg.Snapshot)
kv.lastIncludedIndex = applyMsg.SnapshotIndex
}
kv.mu.Unlock()
}
}