MIT 6.824 Lab2: Raft 笔记

本文详细介绍了Raft分布式一致性算法,包括Leader选举、日志复制和持久化等核心概念。通过分析Raft如何解决Paxos的复杂性问题,以及在Golang中实现相关功能时遇到的挑战,展示了Raft如何确保在服务器故障情况下仍能保持日志一致性和系统稳定性。
摘要由CSDN通过智能技术生成

Raft一致性算法简单图解

简介

  • Raft与Paxos区别:Paxos更难以理解,且在实际应用中无法完全解决出现的各种问题,Raft能够实现与Paxos类似的功能,但Raft更易于理解,适合用于课程教学,且更易于作为其他分布式系统的基础框架
  • 每个Raft server有三个状态:Follower、Candidate、Leader,Follower在达到选举超时后变为Candidate,Candidate在收到大多数选票后变为Leader,在每个任期内若Leader一直存活则应该只有一个Leader
  • Leader向其他Server发送心跳包,以避免其超时选举,Leader负责与Client通信,并在接收到Client请求后将请求命令放到日志里,并在心跳包中更新Follower的日志以达成日志的一致性,在大多数Server日志达成一致后才会提交请求

Part 2A: Leader Election

Leader选举

  • 这部分实现Leader选举,需要保证每个任期只有一个Leader,且Leader宕机后能够选出新的Leader,Server数量不足时当前任期无Leader
  • 当Follwer达到选举超时时重置超时,递增任期并转化为Candidate开始选举,对其他Server发送RequestVote RPC请求,如果接收到大多数选票且没有达到超时,则转化为Leader
  • Leader每隔一段时间对其他Server发送心跳包,以保证其他Server不会超时进入选举
  • 当Leader宕机时,其他Server开始新一轮选举,并选出新的Leader
  • 若收到的RPC参数中的任期小于当前任期则拒绝该请求,若大于当前任期则转化为Follower,发起请求的一方若接收到reply中任期大于当前任期则转化为Follower
  • Golang的select/case可以达到非阻塞i/o的需求,类似于select/epoll,若某个输入得到响应则忽略其他输入转而处理当前输入的响应,利用这点可以对超时进行处理
  • 遇到的坑点有选举时需要给自己投票,否则达不到大多数选票的要求就一直请求却转化不了Leader,还有就是死锁问题,若函数中已经加了锁,函数外就没必要加锁,否则会造成死锁

测试通过截图:
Part 2A

  • Raft数据结构:
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[]

	// Your data here (2A, 2B, 2C).
	// Look at the paper's Figure 2 for a description of what
	// state a Raft server must maintain.
	timer* time.Timer
	timeout time.Duration
	stat int

	appendCh chan struct{
   }
	voteCh chan struct{
   }
	voteCount int

	currentTerm int		//latest term server has seen (initialized to 0 on first boot, increases monotonically)
	voteFor int			//candidateId that received vote in current term (or null if none)
	log[] Entry			//log entries; each entry contains command for state machine, and term when entry was received by leader (first index is 1)

	commitIndex int		//index of highest log entry known to be committed (initialized to 0, increases monotonically)
	lastApplied int		//index of highest log entry applied to state machine (initialized to 0, increases monotonically)
	nextIndex[] int		//for each server, index of the next log entry to send to that server (initialized to leader last log index + 1)
	matchIndex[] int	//for each server, index of highest log entry known to be replicated on server (initialized to 0, increases monotonically)
}
  • 心跳包即空的AppendEntry:
type AppendEntriesArgs struct {
   
	Term int			//leader’s term
	LeaderId int		//so follower can redirect clients
	PrevLogIndex int	//index of log entry immediately preceding new ones
	PrevLogTerm int		//term of prevLogIndex entry
	Entries	[]Entry		//log entries to store (empty for heartbeat; may send more than one for efficiency)
	LeaderCommit int	//leader’s commitIndex
}
type AppendEntriesReply struct {
   
	Term int		//currentTerm, for leader to update itself
	Success bool	//true if follower contained entry matching prevLogIndex and prevLogTerm
}
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
   
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if args.Term < rf.currentTerm {
   
		reply.Success = false
		reply.Term = rf.currentTerm
	} else if args.Term > rf.currentTerm{
   
		rf.currentTerm = args.Term
		rf.stat = Follower
		rf.voteFor = -1
		reply.Success = true
	} else {
   
		reply.Success = true
	}
	go func() {
   
		rf.appendCh <- struct{
   }{
   }
	}()
	/*if rf.log[args.prevLogIndex].term != args.prevLogTerm {
		reply.success = false
	}*/
}
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
   
	ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
	return ok
}
  • RequestVoteRpc:
type RequestVoteArgs struct {
   
	// Your data here (2A, 2B).
	Term int			//candidate’s term
	CandidateId int		//candidate requesting vote
	LastLogIndex int	//index of candidate’s last log entry
	LastLogTerm int		//term of candidate’s last log entry
}
//
// example RequestVote RPC reply structure.
// field names must start with capital letters!
//
type RequestVoteReply struct {
   
	// Your data here (2A).
	Term int			//currentTerm, for candidate to update itself
	VoteGranted bool	//true means candidate received vote
}

//
// example RequestVote RPC handler.
//
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
   
	// Your code here (2A, 2B).
	fmt.Printf("raft%v Lock\n", rf.me)
	rf.mu.Lock()
	defer func() {
   fmt.Printf("raft%v Unlock\n", rf.me); rf.mu.Unlock()}()
	fmt.Printf("args.term:%v, currentTerm:%v, received server:%v, stat:%v\n", args.Term, rf.currentTerm, rf.me, rf.stat)
	if args.Term > rf.currentTerm {
   
		rf.currentTerm = args.Term
		rf.stat = Follower
		rf.voteFor = -1
		reply.VoteGranted = true
	} else if rf.voteFor == -1  /*&& rf.lastApplied == args.lastLogIndex && rf.log[rf.lastApplied].term == args.lastLogTerm */{
   
		reply.VoteGranted = true
		rf.voteFor = args.CandidateId
		fmt.Printf("raft%v vote for raft%v\n", rf.me, args.CandidateId)
	} else {
   
		reply.VoteGranted = false
	}
	if args.Term < rf.currentTerm {
   
		fmt.Printf("raft%v don't vote for raft%v\n", rf.me, args.CandidateId)
		reply.VoteGranted = false
		reply.Term = rf.currentTerm
	}
	go func() {
   
		rf.voteCh <- struct{
   }{
   }
	}()
}
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
   
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}
  • 状态转换函数(便于调试以及简化代码):
func (rf *Raft) updateStatTo(stat int)  {
   
	if rf.stat == stat {
   
		return
	}
	if stat == Follower {
   
		rf.stat = Follower
		fmt.Printf("raft%v become follower\n", rf.me)
		rf.voteFor = -1
	}
	if stat == Candidate {
   
		rf.stat = Candidate
		fmt.Printf("raft%v become candidate\n", rf.me)
		rf.startElection()
	}
	if
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值