MIT6.5840-2023-Lab2B: Raft-Log

前置知识

见上一篇 Lab2A。

实验内容

实现 RAFT,分为四个 part:leader election、log、persistence、log compaction。

实验环境

OS:WSL-Ubuntu-18.04
golang:go1.17.6 linux/amd64

Part 2B: log

这部分主要实现添加新的 log。

日志匹配特性(Log Matching Property)

  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同。

第一个特性来自这样的一个事实,领导人最多在一个任期里在指定的一个日志索引位置创建一条日志条目,同时日志条目在日志中的位置也从来不会改变。第二个特性由附加日志 RPC 的一个简单的一致性检查所保证。在发送附加日志 RPC 的时候,领导人会把新的日志条目前紧挨着的条目的索引位置和任期号包含在日志内。如果跟随者在它的日志中找不到包含相同索引位置和任期号的条目,那么他就会拒绝接收新的日志条目。一致性检查就像一个归纳步骤:一开始空的日志状态肯定是满足日志匹配特性的,然后一致性检查在日志扩展的时候保护了日志匹配特性。因此,每当附加日志 RPC 返回成功时,领导人就知道跟随者的日志一定是和自己相同的了。

Start:
向 leader 添加log;

客户端的每一个请求都包含一条被复制状态机执行的指令。领导人把这条指令作为一条新的日志条目附加到日志中去(此时请求返回指令在日志中的 index,但不保证指令被提交;Start 函数返回了,随着时间的推移,对应于这个客户端请求的 ApplyMsg 从applyCh channel 中出现在了 key-value 层。只有在那个时候,key-value 层才会执行这个请求,并返回响应给客户端),然后并行地发起 AppendEntries RPCs 给其他的服务器,让他们复制这条日志条目。当这条日志条目被安全地复制,领导人会应用这条日志条目到它的状态机中然后把执行的结果返回给客户端。如果跟随者崩溃或者运行缓慢,再或者网络丢包,领导人会不断的重复尝试 AppendEntries RPCs (尽管已经回复了客户端)直到所有的跟随者都最终存储了所有的日志条目。

func (rf *Raft) Start(command interface{}) (int, int, bool) {
	index := -1
	term := -1
	isLeader := true

	// Your code here (2B).
	rf.mu.Lock()
	term = rf.currentTerm
	isLeader = rf.state == Leader
	if !isLeader {
		rf.mu.Unlock()
		return index, term, isLeader
	}
	log.Println(rf.me, "Start", "rf.currentTerm", rf.currentTerm, "command", command, "len(rf.log)", len(rf.log))
	rf.log = append(rf.log, Entry{
		Command: command,
		Term:    term,
	})
	rf.nextIndex[rf.me] = len(rf.log)
	rf.matchIndex[rf.me] = len(rf.log) - 1

	rf.persist()
	index = len(rf.log) - 1
	rf.mu.Unlock()

	go rf.replicateLog() // replicate log to follower

	return index, term, isLeader
}

commit:
更新 commitIndex。

领导人知道一条当前任期内的日志记录是可以被提交的,只要它被存储到了大多数的服务器上。Raft 永远不会通过计算副本数目的方式去提交一个之前任期内的日志条目。只有领导人当前任期里的日志条目通过计算副本数目可以被提交;一旦当前任期的日志条目以这种方式被提交,那么由于日志匹配特性,之前的日志条目也都会被间接的提交。

func (rf *Raft) commit() {
	rf.mu.Lock()
	defer rf.mu.Unlock()

	if rf.state != Leader {
		return
	}

	for N := len(rf.log) - 1; N > rf.commitIndex; N-- {
		if rf.log[N].Term != rf.currentTerm {
			return
		}
		num := 0
		for i := 0; i < len(rf.matchIndex); i++ {
			if rf.matchIndex[i] >= N {
				num++
			}
		}
		if num > len(rf.peers)/2 {
			rf.commitIndex = N
			log.Println(rf.me, "commit", "rf.commitIndex", rf.commitIndex, "len(rf.log)", len(rf.log))
			return
		}
	}
}

apply:
当 commitIndex 落后 lastApplied,向 applyCh 传输 command。

func (rf *Raft) apply() {
	for rf.killed() == false {
		rf.mu.Lock()

		if rf.commitIndex > rf.lastApplied {
			savedLastApplied := rf.lastApplied
			logEntries := rf.log[rf.lastApplied+1 : rf.commitIndex+1]
			rf.lastApplied = rf.commitIndex
			for i, entry := range logEntries {
				log.Println(rf.me, "apply", "rf.commitIndex", rf.commitIndex, "rf.lastApplied", rf.lastApplied, "command", entry.Command)
				rf.applyCh <- ApplyMsg{
					CommandValid: true,
					Command:      entry.Command,
					CommandIndex: savedLastApplied + i + 1,
				}
			}
		}

		// if rf.commitIndex > rf.lastApplied {
		// 	log.Println(rf.me, "apply", "rf.commitIndex", rf.commitIndex, "rf.lastApplied", rf.lastApplied, "command", rf.log[rf.lastApplied+1].Command)
		// 	rf.lastApplied++
		// 	rf.applyCh <- ApplyMsg{
		// 		CommandValid: true,
		// 		Command:      rf.log[rf.lastApplied].Command,
		// 		CommandIndex: rf.lastApplied,
		// 	}
		// }
		rf.mu.Unlock()

		time.Sleep(time.Duration(10) * time.Millisecond)
	}
}

实验结果

在这里插入图片描述

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值