李度、马也驰 25spring数据库系统 p1仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
5.1 KiB

package nodes
import (
"math/rand"
"net/rpc"
"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() {
n.mu.Lock()
defer n.mu.Unlock()
// 增加当前任期,转换为 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 // 自己的票
for peerId := range n.nodes {
go func(peerId string) {
reply := RequestVoteReply{}
if n.sendRequestVote(peerId, &args, &reply) {
mu.Lock()
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()
mu.Unlock()
return
}
if reply.VoteGranted {
grantedVotes++
}
if grantedVotes == totalNodes / 2 + 1 {
n.state = Leader
log.Sugar().Infof("[%s] 当选 Leader!", n.selfId)
n.initLeaderState()
}
mu.Unlock()
}
}(peerId)
}
// 等待选举结果
time.Sleep(300 * time.Millisecond)
mu.Lock()
if n.state == Candidate {
log.Sugar().Infof("[%s] 选举超时,重新发起选举", n.selfId)
// n.state = Follower 这里不修改,如果appendentries收到term合理的心跳,再变回follower
n.resetElectionTimer()
}
mu.Unlock()
}
func (node *Node) sendRequestVote(peerId string, args *RequestVoteArgs, reply *RequestVoteReply) bool {
log.Sugar().Infof("[%s] 请求 [%s] 投票给自己", node.selfId, peerId)
client, err := DialHTTPWithTimeout("tcp", node.nodes[peerId].address)
if err != nil {
log.Error("dialing: ", zap.Error(err))
return false
}
defer func(client *rpc.Client) {
err := client.Close()
if err != nil {
log.Error("client close err: ", zap.Error(err))
}
}(client)
callErr := CallWithTimeout(client, "Node.RequestVote", args, reply) // RPC
if callErr != nil {
log.Error("dialing node_"+peerId+"fail: ", zap.Error(callErr))
}
return callErr == nil
}
func (n *Node) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) error {
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 500-1000ms内没收到appendentries心跳,就变成candidate发起选举
func (node *Node) resetElectionTimer() {
if node.electionTimer == nil {
node.electionTimer = time.NewTimer(time.Duration(500+rand.Intn(500)) * time.Millisecond)
go func() {
for {
<-node.electionTimer.C
node.startElection()
}
}()
} else {
node.electionTimer.Stop()
node.electionTimer.Reset(time.Duration(500+rand.Intn(500)) * time.Millisecond)
}
}