Raft 领导者选举

Raft基础

由于Paxos算法过于复杂,需要一艘具有R{eliable|eplicated|edundant} And Fault-Tolerant的原木(log)组成的木筏(raft)驶出帕加索斯(Paxos)小岛。

Leader 领导者 处理客户端请求和日志复制,同一时刻最多只有一个领导者
Follower 跟随着 被动处理请求,不主动发送RPC请求,只响应收到的RPC请求
Candidate 候选者 用于选举出新的领导者,处于领导者和跟随者之间的暂存状态

Raft算法选举出领导者意味进入一个新任期(Term),实际上任期就是一个逻辑时间。Raft用任期解决分布式系统中的时序问题。任期单调递增且永不重复。

一个正常的任期至少有一个领导者,任期通常分为两部分,任期开始时的选举和任期正常运行的部分。
raft运行

每个服务器需要维护一个currentTerm变量,记录当前已知最新任期号,必须持久化存储currentTerm变量,以便服务器宕机重启后能够知道最新任期。

Raft服务器之间的通信通过2个RPC调用,RequestVote RPC用于领导者选举,AppendEntries RPC用于复制日志和发送心跳。

领导者选举

Raft算法启动时,每个节点都是跟随者,被动接受领导者或候选者的RPC请求。在经过超时时间后(一般每个节点不同,随机在100ms-500ms范围内)没有接受到领导者发送的RPC请求,跟随者会进入候选者状态,请求获取其他跟随者的投票,获得超过半数投票后,该节点成为领导者,向其他节点发送心跳,维持自己的领导者身份。

状态转换

raft中的角色转换

当一个节点开始竞选时,流程如下图所示,第一步,节点转换为候选者状态,目的是获取选票,成为领导者;第二步,增加自己的当前任期变量 currentTerm,表示进入新的任期;第三步,先 给自己投一票;第四步,并行向其他节点方式RequestVote消息索要选票,如果没有收到,会反复尝试,直到发生以下情况:
候选者参与选举
候选者选举过程

(1)获得超过半数的选票。该节点成为领导者,并向其他节点每隔一段时间发送心跳信息(空的AppendEntries),维持自己的领导者身份。
(2)收到来自领导者的AppendEntries心跳消息,说明系统中已经存在领导者,节点转换为跟随者。
(3)选举超时,说明此时没有领导者产生,重新开始新一轮选举。

选举过程需要保证共识算法中的两个特性:安全性活性。安全性指一个任期内只有一个领导者被选举出来;活性指最终会选举出一个领导者。
安全性需要保证:
(1)每个节点在一个任期内只能投一次票,将投给第一个满足条件的RequestVote请求的发送者,然后拒绝其他候选者的请求。需要在每个节点增加一个votedFor变量(需要持久化),表示当前任期投给哪个候选者,如果没有投票,则votedFor为空。votedFor需要持久化,当节点重启恢复后,需要恢复投票信息,否则可以将票投给其他候选者。
(2)只有获取超过半数的选票才能成为领导者,即无法存在两个不同候选者在同一任期内同时成为领导者(因为无法获取 n ÷ 2 + 1 n{\div} 2 +1 n÷2+1的选票)。

=>问题
如果选举从同一时间开始,没有一个候选者拿到多数票,比如8个节点,3个候选者,获得选票为3,3,2,触发超时,继续进行选举,循环往复。为了解决这个问题,可以通过活锁解决问题:每个节点随机选取超时时间,通常在[T,2T]范围内(比如150ms-300ms)。通过活锁,先到达超时时间的节点,转换为候选者,向其他节点请求投票。

如果T远大于网络广播时间,效果更佳。

简单实现

package go_raft

import (
	"sync"
	"time"
)

const (
	Follower = iota
	Candidate
	Leader
)

type Raft struct {
	mu sync.Mutex

	state int

	currentTerm int
	votedFor    int

	log []LogEntry

	heartbeatTime time.Time
}

type RequestVoteArgs struct {
	//候选者任期
	Term int
	//候选者id
	CandidateId int
	//候选者的最后一条日志的索引
	LastLogIndex int
	//候选者的最后一条日志的任期
	LastLogTerm int
}

type RequestVoteReply struct {
	//处理请求节点任期
	Term int
	//是否获得投票
	VoteGranter bool
}

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()

	reply.Term = rf.currentTerm
	reply.VoteGranter = false
	if args.Term < rf.currentTerm {
		return
	}

	//收到更大的请求,更新自己的currentTerm,转变为Follower
	if args.Term > rf.currentTerm {
		rf.currentTerm = args.Term
		rf.state = Follower
		rf.votedFor = -1
	}

	if rf.votedFor == -1 || rf.votedFor == args.CandidateId {
		//增强选举限制
		lastIndex := len(rf.log) - 1
		if rf.log[lastIndex].Term > args.LastLogTerm ||
			(rf.log[lastIndex].Term == args.LastLogTerm && rf.log[lastIndex].Index > args.LastLogIndex) {
			return
		}

		rf.votedFor = args.CandidateId
		reply.VoteGranter = true
		rf.heartbeatTime = time.Now()
	}
	return

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值