MIT 6.824 Part 2B: log

总结

如果用几句话总结part 2B,那就是:
修改pingloop(也就是心跳循环),修改AppendEntries实现日志同步,修改RequestVote(选举要考虑日志term和条数),增加Start(Leader的入口函数,附加日志条目),增加rf.applyCh <- msg(commit日志到channel) 这些缺一不可,除此之外,可能还需要改一下之前关于日志声明、定义有问题的地方。
除论文外参考的文章:

最重要的演示图

框架

一个实现
在这里插入图片描述

需要注意的地方

pingloop

  1. for遍历peers的时候一定要更新自己的matchIndex和nextIndex,用来更新leader commitIndex
  2. Make初始化注意rf.log = make([]LogEntry, 1)确保log下标从1开始,和下图一致
    在这里插入图片描述
  3. getMajoritySameIndex用来在reply为success时更新commitIndex,查找算法为找到peers matchIndex的中位数,更新完之后,对在之前的commitIndex和之后的commitIndex之间的log进行setCommitIndex提交
  4. 如果reply为false,那么跳过一个term重新匹配

AppendEntries

  1. len(rf.log)-1<args.PrevLogIndex直接返回,reply.MatchIndex在2B没有用
  2. args.PrevLogIndex>0 && rf.log[args.PrevLogIndex].Term != args.PrevLogTerm删除不匹配的是可选的,如果不删除也可以通过
  3. 在保存日志的时候有脏数据必须截取append,否则有一个测试点跑不过

RequestVote

被选举人应该保有比选举人更新的数据,下图为false

Start

返回值依次为len(rf.log)、rf.currentTerm、是否为leader

rf.applyCh <- msg

ApplyMsg包括CommandValid、Command、CommandIndex,每次将一条信息通过channel发送给applyCh

代码实现

package raft

//
// this is an outline of the API that raft must expose to
// the service (or tester). see comments below for
// each of these functions for more details.
//
// rf = Make(...)
//   create a new Raft server.
// rf.Start(command interface{}) (index, Term, isleader)
//   start agreement on a new log entry
// rf.GetState() (Term, isLeader)
//   ask a Raft for its current Term, and whether it thinks it is leader
// ApplyMsg
//   each time a new entry is committed to the log, each Raft peer
//   should send an ApplyMsg to the service (or tester)
//   in the same server.
//

import (
	"math/rand"
	"sort"

	//	"bytes"
	"sync"
	"sync/atomic"
	"time"

	//	"6.824/labgob"
	"6.824/labrpc"
)

// ApplyMsg
// as each Raft peer becomes aware that successive log Entries are
// committed, the peer should send an ApplyMsg to the service (or
// tester) on the same server, via the applyCh passed to Make(). set
// CommandValid to true to indicate that the ApplyMsg contains a newly
// committed log entry.
//
// in part 2D you'll want to send other kinds of messages (e.g.,
// snapshots) on the applyCh, but set CommandValid to false for these
// other uses.
//
type ApplyMsg struct {
	CommandValid bool
	Command      interface{}
	CommandIndex int

	// For 2D:
	SnapshotValid bool
	Snapshot      []byte
	SnapshotTerm  int
	SnapshotIndex int
}

const (
	FOLLOWER = iota
	CANDIDATE
	LEADER
)
type LogEntry struct {
	Command interface{}
	//1
	Index 	int
	Term    int
}

//
// A Go object implementing a single Raft peer.
//
type Raft struct {
	mu        sync.Mutex          // Lock to protect shared access to this peer's state
	peers     []*labrpc.ClientEnd // RPC end points of all peers
	persister *Persister          // Object to hold this peer's persisted state
	me        int                 // this peer's index into peers[]
	dead      int32               // set by Kill()

	// Your data here (2A, 2B, 2C).
	// Look at the paper's Figure 2 for a description of what
	// state a Raft server must maintain.
	//persistent for all
	role			int
	currentTerm		int
	votedFor		int
	log 			[]LogEntry
	//volatile for all
	commitIndex		int
	lastApplied		int
	//volatile for leaders
	// 保存要发送的下一个follower条目
	nextIndex     []int
	//跟踪每一个leader和follower匹配到的日志条目
	matchIndex    []int
	electionTimer time.Timer
	lastElectionTimer 	time.Time
	electTime 		int64
	applyCh        chan ApplyMsg // 提交
}

// return currentTerm and whether this server
// believes it is the leader.
func (rf *Raft) GetState() (int, bool) {
	var term int
	var isleader bool
	// Your code here (2A).
	rf.mu.Lock()
	defer rf.mu.Unlock()
	term = rf.currentTerm
	isleader = rf.role==LEADER
	return term, isleader
}
func (rf *Raft) getElectTime() int64 {
	rand.Seed(int64(rf.me)*1000)
	return rand.Int63n(100) + 300
}
func (rf *Raft) becomeCandidate() {
	_, err := DPrintf("%d change to candidate, it role is %d", rf.me,rf.role)
	if err != nil {
		return
	}
	rf.role = CANDIDATE
	rf.currentTerm++
	rf.votedFor = rf.me
	//rf.persist()
}

func (rf *Raft) becomeFollower(term int) {
	rf.role = FOLLOWER
	rf.currentTerm = term
	rf.votedFor = -1
	//rf.persist()
	//rf.lastElectionTimer = time.Now()
	_, err := DPrintf("%d change to follower with Term %d", rf.me, rf.currentTerm)
	if err != nil {
		return
	}
}

func (rf *Raft) becomeLeader() {
	rf.role = LEADER
	//rf.persist()

	for i := 0; i < len(rf.peers); i++ {
		// 同步条目
		rf.matchIndex[i] = 0
		// 从最后一条开始试探
		rf.nextIndex[i] = len(rf.log)
	}

	go rf.pingLoop()
	_, err := DPrintf("%d change to leader", rf.me)
	if err != nil {
		return
	}
}

func (rf *Raft) electionLoop() {
	for !rf.killed() {
		// 等待选举超时
		rf.mu.Lock()
		// 跳过leader
		if rf.role == LEADER {

			rf.mu.Unlock()
		}else if time.Since(rf.lastElectionTimer).Milliseconds() > rf.electTime{
			_, err := DPrintf("%d time out!lastElectionTimer is %d," +
				"electTime is %d will get lock...", rf.me,
				time.Since(rf.lastElectionTimer).Milliseconds(),
				rf.electTime)
			if err != nil {
				return
			}
			//开始选举
			rf.becomeCandidate()
			// request vote from each Peer except itself
			// 发起选举
			lastLogIndex := len(rf.log) - 1
			args := RequestVoteArgs{
				Term:         rf.currentTerm,
				CandidateId:  rf.me,
				LastLogIndex: lastLogIndex,
				LastLogTerm:  rf.log[lastLogIndex].Term,
			}
			var conMutex sync.Mutex
			count := 1
			for server,_ := range rf.peers{
				if server==rf.me{
					rf.nextIndex[server] = len(rf.log)
					rf.matchIndex[server] = len(rf.log) - 1
					continue
				}
				go func(server int){
					reply := RequestVoteReply{}
					ok := rf.SendRequestVote(server, &args, &reply)
					rf.mu.Lock()
					defer rf.mu.Unlock()
					_, err := DPrintf("%d send Request to %d", rf.me, server)
					if err != nil {
						return
					}
					// 注意!处理过期的term
					if ok==false||reply.Term<rf.currentTerm{
						_, err := DPrintf("%d return be", rf.me)
						if err != nil {
							return
						}
						return
					}
					// 更大的term,更新并且变成follower
					if reply.Term>rf.currentTerm{
						rf.currentTerm = reply.Term
						rf.becomeFollower(reply.Term)
						return
					}
					if reply.VoteGranted {
						conMutex.Lock()
						defer conMutex.Unlock()
						//  已经是leader了,跳过
						if rf.role==LEADER{
							return
						}
						count = count + 1
						if count> len(rf.peers)/2 {
							rf.becomeLeader()
							_, err := DPrintf("%d get %d ticket,become leader",rf.me, count)
							if err != nil {
								return
							}
						}
					}
				}(server)
			}
			rf.lastElectionTimer = time.Now()
			rf.electTime = rf.getElectTime()
			rf.mu.Unlock()
		}else{
			rf.mu.Unlock()
		}
		time.Sleep(80 * time.Millisecond)
	}
}
func getMajoritySameIndex(matchIndex []int) int{
	tmp := make([]int,len(matchIndex))
	copy(tmp,matchIndex)
	sort.Sort(sort.Reverse(sort.IntSlice(tmp)))
	idx := len(tmp)/2
	return tmp[idx]
}
func (rf *Raft) pingLoop() {
	for {
		rf.mu.Lock()
		// 如果不是leader退出循环
		if rf.role != LEADER {
			_, err := DPrintf("%d not leader!",rf.me)
			if err != nil {
				return
			}
			rf.mu.Unlock()
			return
		}

		for server,_ := range rf.peers{
			//此處一定要更新自己的nextIndex和matchIndex
			// 作用是确保commitIndex的正确更新
			if server==rf.me{
				rf.nextIndex[server] = len(rf.log)
				rf.matchIndex[server] = len(rf.log) - 1
				continue
			}
			DPrintf("leader %d ,server %d,nextIndex %d,matchIndex %d,log size %d" ,
				rf.me,server,rf.nextIndex[server],rf.matchIndex[server],len(rf.log))
			args := AppendEntriesArgs{
				Term : rf.currentTerm,
				LeaderId : rf.me,
				LeaderCommit : rf.commitIndex,
				PrevLogIndex :rf.nextIndex[server]-1,
				PrevLogTerm : rf.log[(rf.nextIndex[server]-1)].Term,
			}
			args.Entries = make([]LogEntry, len(rf.log[(args.PrevLogIndex+1):]))
			copy(args.Entries, rf.log[(args.PrevLogIndex+1):])
			args.PrevLogTerm = rf.log[args.PrevLogIndex].Term
			rf.mu.Unlock()
			go func(server int){
				reply := AppendEntriesReply{}
				ok := rf.SendAppendEntries(server, &args, &reply)
				rf.mu.Lock()
				defer rf.mu.Unlock()
				if ok==false {
					return
				}

				if reply.Term>rf.currentTerm{
					rf.becomeFollower(reply.Term)
				}
				// 注意!判断Term 上下兩個判斷的位置不能顛倒
				if rf.role != LEADER || rf.currentTerm!=args.Term{
					return
				}
				if reply.Success {
					rf.matchIndex[server] = args.PrevLogIndex + len(args.Entries) // do not depend on len(rf.log)
					rf.nextIndex[server] = rf.matchIndex[server] + 1
					// 每次更新Peer的匹配
					majorityIndex := getMajoritySameIndex(rf.matchIndex)
					DPrintf("majorityIndex:%d",majorityIndex)
					if len(args.Entries) != 0 && rf.log[majorityIndex].Term == rf.currentTerm && majorityIndex > rf.commitIndex {
						for N:=rf.commitIndex;N<=majorityIndex;N++{
							rf.setCommitIndex(N)
						}
						rf.commitIndex = majorityIndex
						DPrintf("%v advance commit index to %v", rf, rf.commitIndex)
					}

				} else {
					//優化策略:如果不匹配,那麼跳過一個term
					prevIndex := args.PrevLogIndex
					for prevIndex > 0 && rf.log[prevIndex].Term == args.PrevLogTerm {
						prevIndex--
					}
					rf.nextIndex[server] = prevIndex + 1
				}

			}(server)
			rf.mu.Lock()
		}
		rf.mu.Unlock()
		time.Sleep(100*time.Millisecond)
	}
}
//func (rf *Raft) applyLoop() {
//	for {
//		time.Sleep(10 * time.Millisecond)
//		rf.mu.Lock()
//		for rf.lastApplied < rf.commitIndex {
//			rf.lastApplied++
//			rf.apply( rf.lastApplied,rf.log[rf.lastApplied]) // put to applyChan in the function
//		}
//		rf.mu.Unlock()
//	}
//}

//
// save Raft's persistent state to stable storage,
// where it can later be retrieved after a crash and restart.
// see paper's Figure 2 for a description of what should be persistent.
//
func (rf *Raft) persist() {
	// Your code here (2C).
	// Example:
	// w := new(bytes.Buffer)
	// e := labgob.NewEncoder(w)
	// e.Encode(rf.xxx)
	// e.Encode(rf.yyy)
	// data := w.Bytes()
	// rf.persister.SaveRaftState(data)
}


//
// restore previously persisted state.
//
func (rf *Raft) readPersist(data []byte) {
	if data == nil || len(data) < 1 { // bootstrap without any state?
		return
	}
	// Your code here (2C).
	// Example:
	// r := bytes.NewBuffer(data)
	// d := labgob.NewDecoder(r)
	// var xxx
	// var yyy
	// if d.Decode(&xxx) != nil ||
	//    d.Decode(&yyy) != nil {
	//   error...
	// } else {
	//   rf.xxx = xxx
	//   rf.yyy = yyy
	// }
}


//
// A service wants to switch to snapshot.  Only do so if Raft hasn't
// have more recent info since it communicate the snapshot on applyCh.
//
func (rf *Raft) CondInstallSnapshot(lastIncludedTerm int, lastIncludedIndex int, snapshot []byte) bool {

	// Your code here (2D).

	return true
}

// the service says it has created a snapshot that has
// all info up to and including index. this means the
// service no longer needs the log through (and including)
// that index. Raft should now trim its log as much as possible.
func (rf *Raft) Snapshot(index int, snapshot []byte) {
	// Your code here (2D).

}


//
// example RequestVote RPC arguments structure.
// field names must start with capital letters!
//
type RequestVoteArgs struct {
	// Your data here (2A, 2B).
	Term			int
	CandidateId		int
	LastLogIndex 	int
	LastLogTerm		int
}

//
// example RequestVote RPC reply structure.
// field names must start with capital letters!
//
type RequestVoteReply struct {
	// Your data here (2A).
	Term 	int
	VoteGranted	bool
}


func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {

	// Your code here (2A, 2B).
	rf.mu.Lock()
	defer rf.mu.Unlock()
	_, err := DPrintf("%d RequestVote get from %d", rf.me,args.CandidateId)
	if err != nil {
		return
	}
	// 拒绝投票 term比較小 或者已經投過票了
	if args.Term<=rf.currentTerm ||
		(args.Term == rf.currentTerm && rf.votedFor != -1 && rf.votedFor != args.CandidateId){
		_, err := DPrintf("confuse vote to %d",args.CandidateId)
		if err != nil {
			return
		}
		reply.VoteGranted = false
		reply.Term = rf.currentTerm
		return
	}
	// 转换为follower
	if args.Term > rf.currentTerm{
		_, err := DPrintf("%d become to follower because of bigger term",rf.me)
		if err != nil {
			return
		}
		//转换为follower
		rf.becomeFollower(args.Term)
	}
	// 2B: candidate's vote should be at least up-to-date as receiver's log
	// "up-to-date" is defined in thesis 5.4.1
	// 被選舉人應當保有比選舉人更新的記錄
	lastLogIndex := len(rf.log) - 1
	if args.LastLogTerm < rf.log[lastLogIndex].Term ||
		(args.LastLogTerm == rf.log[lastLogIndex].Term &&
			args.LastLogIndex < (lastLogIndex)) {
		// Receiver is more up-to-date, does not grant vote
		reply.Term = rf.currentTerm
		reply.VoteGranted = false
		return
	}
	//更新Term
	reply.Term = rf.currentTerm

	//每个任期只能投票给一个人
	//2b 對比log的更新
	if rf.votedFor == -1 {
		_, err := DPrintf("%d vote to %d",rf.me, args.CandidateId)
		if err != nil {
			return
		}
		reply.VoteGranted = true
		rf.votedFor = args.CandidateId
		// 授予投票也要更新时间
		rf.lastElectionTimer = time.Now()
		// 投票之后再次更新Term!
		reply.Term = rf.currentTerm
	}
	rf.persist()
}
func (rf *Raft) SendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}

type AppendEntriesArgs struct {
	Term int
	LeaderId int
	PrevLogIndex int
	PrevLogTerm	int
	Entries      []LogEntry
	LeaderCommit int
}

type AppendEntriesReply struct{
	Term    int
	Success    bool
	MatchIndex int
}

func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply){
	rf.mu.Lock()
	defer rf.mu.Unlock()
	// 小于当前任
	reply.Success = false
	reply.MatchIndex = -1
	reply.Term = rf.currentTerm
	if args.Term<rf.currentTerm {
		reply.Term = rf.currentTerm
		return
	}
	// 发现更大的任期,则转为该任期的follower
	if args.Term > rf.currentTerm {
		rf.becomeFollower(args.Term)
	}
	// 不匹配返回重新匹配
	if len(rf.log)-1<args.PrevLogIndex{
		reply.MatchIndex = len(rf.log)
		return
	}
	//term不同的情况
	if args.PrevLogIndex>0 && rf.log[args.PrevLogIndex].Term != args.PrevLogTerm {
		rf.log = rf.log[0:args.PrevLogIndex] // delete the log in prevLogIndex and after it
		rf.persist()
		return
	}

	// 保存日志
	for i,logEntry := range args.Entries{
		index := args.PrevLogIndex + i +1
		if index > len(rf.log)-1{
			rf.log = append(rf.log, logEntry)
			DPrintf("rf.me %d,get entry,log size %d", rf.me, len(rf.log))
		}else{
			// 防止脏数据,任期不一致的时候截取重新append
			if rf.log[index].Term != logEntry.Term{
				rf.log = rf.log[:index]
				rf.log = append(rf.log, logEntry)
			}
		}
	}
	if args.LeaderCommit>rf.commitIndex{
		if len(rf.log)-1 < args.LeaderCommit{
			rf.commitIndex = len(rf.log)-1
		}else{
			rf.commitIndex = args.LeaderCommit
		}
		rf.setCommitIndex(rf.commitIndex)
	}
	// 正确的心跳包
	reply.Success = true
	rf.lastElectionTimer = time.Now()
	//日志操作
	rf.persist()
}
func (rf *Raft) SendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {

	ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
	return ok
}

/*
	Start入口函数,应用层消息从此处发送给leader
*/
func (rf *Raft) Start(command interface{}) (int, int, bool) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	index := -1
	term := -1
	term = rf.currentTerm
	isLeader := rf.role==LEADER
	if isLeader{
		index = len(rf.log)
		rf.log = append(rf.log, LogEntry{
			Command: command,
			Index: index,
			Term: term,
		})
		DPrintf("leader %d ,get command %d", rf.me, index)
	}

	// Your code here (2B).

	return index, term, isLeader
}


//
// the tester doesn't halt goroutines created by Raft after each test,
// but it does call the Kill() method. your code can use killed() to
// check whether Kill() has been called. the use of atomic avoids the
// need for a lock.
//
// the issue is that long-running goroutines use memory and may chew
// up CPU time, perhaps causing later tests to fail and generating
// confusing debug output. any goroutine with a long-running loop
// should call killed() to check whether it should stop.
//
func (rf *Raft) Kill() {
	atomic.StoreInt32(&rf.dead, 1)
	// Your code here, if desired.
}

func (rf *Raft) killed() bool {
	z := atomic.LoadInt32(&rf.dead)
	return z == 1
}

// The ticker go routine starts a new election if this peer hasn't received
// heartsbeats recently.
func (rf *Raft) ticker() {
	for rf.killed() == false {

		// Your code here to check if a leader election should
		// be started and to randomize sleeping time using
		// time.Sleep().

	}
}

// Make
// the service or tester wants to create a Raft server. the ports
// of all the Raft servers (including this one) are in peers[]. this
// server's port is peers[me]. all the servers' peers[] arrays
// have the same order. persister is a place for this server to
// save its persistent state, and also initially holds the most
// recent saved state, if any. applyCh is a channel on which the
// tester or service expects Raft to send ApplyMsg messages.
// Make() must return quickly, so it should start goroutines
// for any long-running work.
//
func Make(peers []*labrpc.ClientEnd, me int,
	persister *Persister, applyCh chan ApplyMsg) *Raft {
	rf := &Raft{}
	rf.peers = peers
	rf.persister = persister
	rf.me = me
	rf.matchIndex = make([]int,len(peers))
	rf.nextIndex = make([]int,len(peers))
	// Your initialization code here (2A, 2B, 2C).
	//一开始的角色是FOLLOWER
	rf.role = FOLLOWER
	rf.votedFor = -1
	rf.commitIndex = 0
	rf.lastApplied = 0
	rf.currentTerm = 0
	// 注意!log從1開始
	rf.log = make([]LogEntry, 1)
	// initialize from state persisted before a crash
	rf.readPersist(persister.ReadRaftState())

	// start ticker goroutine to start elections
	//go rf.ticker()
	//设置选举过期时间
	rf.lastElectionTimer = time.Now()
	rf.electTime = rf.getElectTime()
	rf.applyCh = applyCh
	//开始选举循环
	go rf.electionLoop()

	return rf
}

// rf.applyCh 不要忘记在 Make 函数中使用传入的参数 applyCh 进行赋值
func (rf *Raft) setCommitIndex(commitIndex int) {
	rf.commitIndex = commitIndex
	// apply all entries between lastApplied and committed
	// should be called after commitIndex updated
	if rf.commitIndex > rf.lastApplied {
		entriesToApply := append([]LogEntry{}, rf.log[(rf.lastApplied+1):(rf.commitIndex+1)]...)
		go func(startIdx int, entries []LogEntry) {
			for idx, entry := range entries {
				var msg ApplyMsg
				msg.CommandValid = true
				msg.Command = entry.Command
				msg.CommandIndex = startIdx + idx
				rf.applyCh <- msg
				// do not forget to update lastApplied index
				// this is another goroutine, so protect it with lock
				rf.mu.Lock()
				if rf.lastApplied < msg.CommandIndex {
					rf.lastApplied = msg.CommandIndex
				}
				rf.mu.Unlock()
			}
		}(rf.lastApplied+1, entriesToApply)
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值