MIT6.824(lab3B-snapshot)

本文详细介绍了raft一致性算法中如何实现快照功能,包括在kv层开启快照发送loop,raft层的TakeSnapshot和ExceedLogSize函数,以及处理快照的applyLoop和appendEntriesLoop。此外,还讨论了快照处理可能导致的死锁问题及解决策略。

枯木逢春不在茂
年少且惜镜边人

写在前面

lab3B需要实现一个快照功能,这需要对raft的代码需要进行更改,比如在发送心跳的时候,如果nextindex是快照里面的内容则就需要发送快照给peer,持久化也要进行修改,算是对raft加整个系统进行了大优化吧,要独立完成这个任务,很难(对我来说)。所以还是参考了大佬的代码。

实现过程

首先在Raft层引入了LastIncludedIndex和LastIncludedTerm两个持久化状态

kv层需要开启快照发送loop,这里注意判断raft log长度时不要加KV锁,否则会出现Raft和KV层死锁

func (kv *KVServer) snapshotLoop() {
   
   
	for !kv.killed() {
   
   
		var snapshot []byte
		var lastIncludedIndex int
		// 锁内dump snapshot
		func() {
   
   
			// 如果raft log超过了maxraftstate大小,那么对kvStore快照下来
			if kv.maxraftstate != -1 && kv.rf.ExceedLogSize(kv.maxraftstate) {
   
   	// 这里调用ExceedLogSize不要加kv锁,否则会死锁
				// 锁内快照,离开锁通知raft处理
				kv.mu.Lock()
				w := new(bytes.Buffer)
				e := labgob.NewEncoder(w)
				e.Encode(kv.kvStore)	// kv键值对
				e.Encode(kv.seqMap)	// 当前各客户端最大请求编号,也要随着snapshot走
				snapshot = w.Bytes()
				lastIncludedIndex = kv.lastAppliedIndex
				DPrintf("KVServer[%d] KVServer dump snapshot, snapshotSize[%d] lastAppliedIndex[%d]", kv.me, len(snapshot), kv.lastAppliedIndex)
				kv.mu.Unlock()
			}
		}()
		// 锁外通知raft层截断,否则有死锁
		if snapshot != nil {
   
   
			// 通知raft落地snapshot并截断日志(都是已提交的日志,不会因为主从切换截断,放心操作)
			kv.rf.TakeSnapshot(snapshot, lastIncludedIndex)
		}
		time.Sleep(10 * time.Millisecond)
	}
}

sever层检测raft日志长度,超过日志时,则直接打包快照,下降到让raft进行截断。

// 保存snapshot,截断log
func (rf *Raft) TakeSnapshot(snapshot []byte, lastIncludedIndex int) {
   
   
	rf.mu.Lock()
	defer rf.mu.Unlock()
 
	// 已经有更大index的snapshot了
	if lastIncludedIndex <= rf.lastIncludedIndex {
   
   
		return
	}
 
	// 快照的当前元信息
	DPrintf("RafeNode[%d] TakeSnapshot begins, IsLeader[%v] snapshotLastIndex[%d] lastIncludedIndex[%d] lastIncludedTerm[%d]",
		rf.me, rf.leaderId==rf.me, lastIncludedIndex, rf.lastIncludedIndex, rf.lastIncludedTerm)
 
	// 要压缩的日志长度
	compactLogLen := lastIncludedIndex - rf.lastIncludedIndex
 
	// 更新快照元信息
	rf.lastIncludedTerm = rf.log[rf.index2LogPos(lastIncludedIndex)].Term
	rf.lastIncludedIndex = lastIncludedIndex
 
	// 压缩日志
	afterLog := make([]LogEntry, len(rf.log) - compactLogLen)
	copy(afterLog, rf.log[compactLogLen:])
	rf.log = afterLog
 
	// 把snapshot和raftstate持久化
	rf.persister.SaveStateAndSnapshot(rf.raftStateForPersist(), snapshot)
 
	DPrintf("RafeNode[%d] TakeSnapshot ends, IsLeader[%v] snapshotLastIndex[%d] lastIncludedIndex[%d] lastIncludedTerm[%d]",
		rf.me, rf.leaderId==rf.me, lastIncludedIndex, rf.lastIncludedIndex, rf.lastIncludedTerm)
}

截断日志,并且更新快照raft的lastIncludedTerm和lastIncludedIndex信息。

// 日志是否需要压缩
func (rf *Raft) ExceedLogSize(logSize int) bool {
   
   
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.persister.RaftStateSize() >= logSize {
   
   
		return true
	}
	return false
}

raftstatesize=log+currentTerm+voteFor加起来的大小

kV层安装快照处理

func (kv *KVServer) applyLoop() {
   
   
	for !kv.killed() {
   
   
		select {
   
   
		case msg := <-kv.applyCh:
			// 如果是安装快照
			if !msg.CommandValid {
   
   
				func() {
   
   
					kv.mu.Lock()
					defer kv.mu.Unlock()
					if len(msg.Snapshot) == 0 {
   
   	// 空快照,清空数据
						kv.kvStore = make(map[string]string)
						kv.seqMap = make(map[int64]int64)
					} else {
   
   
						// 反序列化快照, 安装到内存
						r := bytes.NewBuffer(msg.Snapshot)
						d := labgob.NewDecoder(r)
						d.Decode(&kv.kvStore)
						d.Decode(&kv.seqMap)
					}
					// 已应用到哪个索引
					kv.lastAppliedIndex = msg.LastIncludedIndex
					DPrintf("KVServer[%d] installSnapshot, kvStore[%v], seqMap[%v] lastAppliedIndex[%v]", kv.me, len(kv.kvStore), len(kv.seqMap), kv.lastAppliedIndex)
				}()
			} else {
   
    // 如果是普通log
				cmd := msg.Command
				index := msg.CommandIndex
 
				func() {
   
   
					kv.mu.Lock()
					defer kv.mu.Unlock()
 
					// 更新已经应用到的日志
					kv.lastAppliedIndex = index
 
					// 操作日志
					op := cmd.(*Op)
 
					opCtx, existOp := kv.reqMap[index]
					prevSeq, existSeq := kv.seqMap[op.ClientId]
					kv.seqMap[op.ClientId] = op.SeqId
 
					if existOp {
   
    // 存在等待结果的RPC, 那么判断状态是否与写入时一致
						if opCtx.op.Term != op.Term {
   
   
							opCtx.wrongLeader = true
						}
					}
 
					// 只处理ID单调递增的客户端写请求
					if op.Type == OP_TYPE_PUT || op.Type == OP_TYPE_APPEND {
   
   
						if !existSeq || op.SeqId > prevSeq {
   
    // 如果是递增的请求ID,那么接受它的变更
							if op
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值