Raft协议详解与实战

一致性

分布式系统最核心问题:维持多个节点副本的一致性。一致性协议通常基于replicated state machines,即所有结点都从同一个state出发,都经过同样的一些操作序列(log),最后到达同样的state。
在这里插入图片描述

  • 状态机:当我们说一致性的时候,实际就是在说要保证这个状态机的一致性。状态机会从log里面取出所有的命令,然后执行一遍,得到的结果就是我们对外提供的保证了一致性的数据
  • Log: 保存了所有修改记录
  • 一致性模块: 一致性模块算法就是用来保证写入的log的命令的一致性,这也是raft算法核心内容

一致性算法的特性

  • safety: 在非拜占庭条件下不会返回不正确的结果(网络延迟、分区、丢包、重复、顺序重排)
  • available:前提大多数节点存活
  • do not depend on timing to ensure the consistency of the logs
  • a command can complete as soon as a majority of the cluster has responded to a single round of remote procedure calls

Raft状态和状态的转化

Raft采用问题分解法状态简化法对一致性算法进行了简化,每个Raft实例会在以下三种状态下进行转化:

  • Leader:所有请求的处理者,Leader副本接受client的更新请求,本地处理后再同步至多个其他副本;
  • Follower:请求的被动更新者,从Leader接受更新请求,然后写入本地日志文件
  • Candidate:如果Follower副本在一段时间内没有收到Leader副本的心跳,则判断Leader可能已经故障,此时启动选主过程,此时副本会变成Candidate状态,直到选主结束。

每种状态都可以用一个统一的Raft实例表示,用state属性来标识当前Raft所在的状态,按照论文中属性来定义Raft实例:
在这里插入图片描述

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.
	currentTerm    int         //2A
	votedFor       int         //2A
	electionTimer  *time.Timer //2A
	heartbeatTimer *time.Timer //2A
	state          NodeState   //2A

	logs         []LogEntry    //2B
	commitIndex int           //2B
	lastApplied int           //2B
	nextIndex   []int         //2B
	matchIndex  []int         //2B
	applyCh     chan ApplyMsg //2B
}

Raft状态的转化过程

在这里插入图片描述
什么时候转化为Candidate

  • follower -> candidate: follower的选举超时时间(electionTimer)到
  • candidate -> candidate: candidate在选举时间内没有赢得选举并且也没有收到来自新leader的心跳

转化为candidate要开始进行选举。

什么时候转化为Leader

  • candiadte -> leader: candidate 在获得majority选票时转化为leader

转化为leader后:初始化nextIndex[], 停止选举超时定时器、向其他server发送心跳、发送完心跳后也要重置心跳定时器(heartbeatTimer)

什么时候转化为Follower

  • leader -> follower
  • candidate -> follower

无论对于leader还是candidate转化为follower的前提就是在RequestVoteRPC和AppendEntriesRPC的request或者response中的term 大于 currentTerm.
转化为follower后心跳定时器停止(对于原来是leader的情况)、重置选举定时器、投票值voteFor清空

根据上述介绍可以定义状态转化方法convertTo, 完成状态转化后的操作,并在相应的情况下进行convertTo的调用

func (rf *Raft) convertTo(state NodeState) {
   
	if rf.state == state {
   
		return
	}
	DPrintf("Term %d: server %d convert from %v to %v\n",
		rf.currentTerm, rf.me, rf.state, state)
	// update state
	rf.state = state

	switch state {
   
	case Follower:
		rf.heartbeatTimer.Stop() //if native server is leader, stop heartbeat
		rf.electionTimer.Reset(randomTime(ElectionTimeoutLower, ElectionTimeoutHigher))
		rf.votedFor = -1 //clear votedFor
	case Candidate:
		rf.startElection()
	case Leader:
		for i := range rf.nextIndex {
   
			rf.nextIndex[i] = len(rf.logs)
		}

		rf.electionTimer.Stop()
		rf.broadcastHeartbeat()
		rf.heartbeatTimer.Reset(HeartbeatInterval)
	}
}

两个定时器
选举定时器(electionTimer), 当出现以下四种情况需要重置定时器:

  • candidate或者leader转化为follower
  • candidate或者follower收到来自leader的心跳
  • candidate开始进行选举
  • 投票给除自身以外的candidate

心跳定时器(heartbeatTimer), 当leader发送完心跳后重置

Raft实例初始化

主要完成Raft实例属性的初始化,并kick off 一个 goroutine 来处理 timer 相关的事件,注意加锁。有几个点需要注意:

  • 所有的Raft实例都初始化为Follower
  • logs[]的index要从1开始rf.logs = make([]LogEntry, 1): logs 从第 1 个开始,这样第 0 个必然是所有节点都相同的。在发现log不一致时,nextIndex减一并在重试时不会越界。
  • nextIndex[]初始化为len(logs)
  • 采用select监听两个定时器,定时时间到完成相应的操作,并注意在读取state时加锁
func Make(peers []*labrpc.ClientEnd, me int,
	persister *Persister, applyCh chan ApplyMsg) *Raft {
   
	rf := &Raft{
   }
	rf.peers = peers
	rf.persister = persister
	rf.me = me
	//2A
	rf.currentTerm = 0
	rf.votedFor = -1
	rf.electionTimer = time.NewTimer(randomTime(ElectionTimeoutLower, ElectionTimeoutHigher))
	rf.heartbeatTimer = time.NewTimer(HeartbeatInterval)
	rf.state = Follower

	//2B
	rf.applyCh = applyCh
	rf.commitIndex = 0
	rf.lastApplied = 0
	rf.logs = make([]LogEntry, 1)
	rf.matchIndex = make([]int, len(rf.peers))
	rf.nextIndex = make([]int, len(rf.peers))
	for i := range rf.nextIndex {
   
		rf.nextIndex[i] = len(rf.logs)
	}

	//2c persist
	// initialize from state persisted before a crash
	//rf.readPersist(persister.ReadRaftState())
	rf.mu.Lock()
	rf.readPersist(persister.ReadRaftState())
	rf.mu.Unlock()

	go func(node *Raft) {
   
		for {
   
			select {
   
			case <-rf.electionTimer.C:
				rf.mu.Lock()
				if rf.state == Follower {
    //there are two situations: Follower and Candidate
					rf.convertTo(Candidate)
				} else {
   
					rf.startElection()
				}
				rf
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值