package nodes import ( "math/rand" "simple-kv-store/internal/logprovider" "strconv" "sync" "time" "go.uber.org/zap" ) type RequestVoteArgs struct { Term int // 候选人的当前任期 CandidateId string // 候选人 ID LastLogIndex int // 候选人最后一条日志的索引 LastLogTerm int // 候选人最后一条日志的任期 } type RequestVoteReply struct { Term int // 当前节点的最新任期 VoteGranted bool // 是否同意投票 } func (n *Node) StartElection() { defer logprovider.DebugTraceback("startElection") n.Mu.Lock() if n.IsFinish { n.Mu.Unlock() return } // 增加当前任期,转换为 Candidate n.CurrTerm++ n.State = Candidate n.VotedFor = n.SelfId // 自己投自己 n.Storage.SetTermAndVote(n.CurrTerm, n.VotedFor) log.Sugar().Infof("[%s] 开始选举,当前任期: %d", n.SelfId, n.CurrTerm) // 重新设置选举超时,防止重复选举 n.ResetElectionTimer() // 构造 RequestVote 请求 var lastLogIndex int var lastLogTerm int if len(n.Log) == 0 { lastLogIndex = 0 lastLogTerm = 0 // 论文中定义,空日志时 Term 设为 0 } else { lastLogIndex = len(n.Log) - 1 lastLogTerm = n.Log[lastLogIndex].Term } args := RequestVoteArgs{ Term: n.CurrTerm, CandidateId: n.SelfId, LastLogIndex: lastLogIndex, LastLogTerm: lastLogTerm, } // 并行向其他节点发送请求投票 var Mu sync.Mutex totalNodes := len(n.Nodes) grantedVotes := 1 // 自己的票 currTerm := n.CurrTerm currState := n.State n.Mu.Unlock() for _, peerId := range n.Nodes { go func(peerId string) { defer logprovider.DebugTraceback("vote") var reply RequestVoteReply if n.sendRequestVote(peerId, &args, &reply) { Mu.Lock() defer Mu.Unlock() n.Mu.Lock() defer n.Mu.Unlock() if currTerm != n.CurrTerm || currState != n.State { return } if reply.Term > n.CurrTerm { // 发现更高任期,回退为 Follower log.Sugar().Infof("[%s] 发现更高的 Term (%d),回退为 Follower", n.SelfId, reply.Term) n.CurrTerm = reply.Term n.State = Follower n.VotedFor = "" n.Storage.SetTermAndVote(n.CurrTerm, n.VotedFor) n.ResetElectionTimer() return } if reply.VoteGranted { grantedVotes++ } if grantedVotes == totalNodes / 2 + 1 { n.State = Leader log.Sugar().Infof("[%s] 当选 Leader!", n.SelfId) n.initLeaderState() } } }(peerId) } // 等待选举结果 time.Sleep(300 * time.Millisecond) Mu.Lock() defer Mu.Unlock() n.Mu.Lock() defer n.Mu.Unlock() if n.State == Candidate { log.Sugar().Infof("[%s] 选举超时,等待后将重新发起选举", n.SelfId) // n.State = Follower 这里不修改,如果appendentries收到term合理的心跳,再变回follower n.ResetElectionTimer() } } func (node *Node) sendRequestVote(peerId string, args *RequestVoteArgs, reply *RequestVoteReply) bool { log.Sugar().Infof("[%s] 请求 [%s] 投票", node.SelfId, peerId) client, err := node.Transport.DialHTTPWithTimeout("tcp", node.SelfId, peerId) if err != nil { log.Error("[" + node.SelfId + "]dialing [" + peerId + "] fail: ", zap.Error(err)) return false } defer func(client ClientInterface) { err := client.Close() if err != nil { log.Error("client close err: ", zap.Error(err)) } }(client) callErr := node.Transport.CallWithTimeout(client, "Node.RequestVote", args, reply) // RPC if callErr != nil { log.Error("[" + node.SelfId + "]calling [" + peerId + "] fail: ", zap.Error(callErr)) } return callErr == nil } func (n *Node) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) error { defer logprovider.DebugTraceback("requestVote") n.Mu.Lock() defer n.Mu.Unlock() // 如果候选人的任期小于当前任期,则拒绝投票 if args.Term < n.CurrTerm { reply.Term = n.CurrTerm reply.VoteGranted = false return nil } // 如果请求的 Term 更高,则更新当前 Term 并回退为 Follower if args.Term > n.CurrTerm { n.CurrTerm = args.Term n.State = Follower n.VotedFor = "" n.ResetElectionTimer() // 重新设置选举超时 } // 检查是否已经投过票,且是否投给了同一个候选人 if n.VotedFor == "" || n.VotedFor == args.CandidateId { // 检查日志是否足够新 var lastLogIndex int var lastLogTerm int if len(n.Log) == 0 { lastLogIndex = -1 lastLogTerm = 0 } else { lastLogIndex = len(n.Log) - 1 lastLogTerm = n.Log[lastLogIndex].Term } if args.LastLogTerm > lastLogTerm || (args.LastLogTerm == lastLogTerm && args.LastLogIndex >= lastLogIndex) { // 够新就投票给候选人 n.VotedFor = args.CandidateId log.Sugar().Infof("在term(%s), [%s]投票给[%s]", strconv.Itoa(n.CurrTerm), n.SelfId, n.VotedFor) reply.VoteGranted = true n.ResetElectionTimer() } else { reply.VoteGranted = false } } else { reply.VoteGranted = false } n.Storage.SetTermAndVote(n.CurrTerm, n.VotedFor) reply.Term = n.CurrTerm return nil } // follower 一段时间内没收到appendentries心跳,就变成candidate发起选举 func (node *Node) ResetElectionTimer() { node.MuElection.Lock() defer node.MuElection.Unlock() if node.ElectionTimer == nil { node.ElectionTimer = time.NewTimer(node.RTTable.GetElectionTimeout()) go func() { defer logprovider.DebugTraceback("reset") for { <-node.ElectionTimer.C node.StartElection() } }() } else { node.ElectionTimer.Stop() node.ElectionTimer.Reset(time.Duration(500+rand.Intn(500)) * time.Millisecond) } }