简介
- 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,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,还有就是死锁问题,若函数中已经加了锁,函数外就没必要加锁,否则会造成死锁
测试通过截图:
- 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