版权声明:本文为博主原创文章,未经博主容许不得转载。算法
手动码字不易,请你们尊重劳动成果,谢谢服务器
Raft在Leader选举阶段使用term编号做为提案编号来执行paxos算法进行leader选举,为了防止活锁出现,Raft算法使用了随机定时器的策略避开了同时竞争Leader的可能。在日至提交阶段,算法保证了Leader要拥有所有日志,而且Client只能与Leader交流,提交日志。Leader利用两阶段提交和大多数集合确认的方式来确保日志被成功保存。其算法和Zookeeper的ZAB算法有必定类似性。学习
不一样于Paxos算法,Raft算法中只有一种角色。这个角色能够有三种状态:
一、Follower
二、Candidate
三、Leader.net
在每台机器上都会存储:
一、currentTerm:当前节点所能看到的最大的term值,该值单调增长
二、votedFor:当前term里将票投给的对象,若是还没有投票则为空
三、log[]:日志条目,会按顺序做用于状态机
四、commitIndex:当前节点最后一个被提交的日志序号
五、lastApplied:当前节点最后一条被应用于状态机的日志序号,若是发现当前机器commitIndex > lastApplied则应该将本机log[]中序号为(lastApplied, commitIndex]的部分应用到状态机
六、snapshot:若是作了日志快照则会存储快照镜像日志
在状态为Leader机器上会额外存储:
一、nextIndex[]:针对每一个其余节点,下一个须要发送的日志的序号
二、matchIndex[]:针对每一个其余节点,当前所知的和Leader匹配的最大日志编号code
一、Leader:当前集群中的领导者(干活最多的),一个Raft集群只能有一个Leader持久存在
二、Candidate:Leader候选人,当接收到多数投票后会成为Leader
三、Follower:跟随者,接受Leader的日志存储、应用请求,拥护Leader的地位
四、term:Leader的选举周期(假设全部机器都是从0开始),在一个Raft集群内单调递增。在一个term周期内,只能有一个Leader当选。这个概念和paxos算法中的提案号一致,在选举过程当中使用paxos算法,将term做为提案号申请本身做为Leader。
五、State machine replication:Raft使用状态机复制来实现日志的一致和容错,对多个相同的状态机施以相同的事件序列,所获得的最终状态也是一致的
。
六、Client:客户端,Raft集群的服务对象,客户端会请求Raft集群去存储本身的信息来保证分布式一致性。对象
一、RequestVote:请求其余节点投票给本身
请求参数:
term:当前节点选举周期term值
candidateId:当前节点编号
lastLogIndex:当前节点最后一个日志的索引
lastLogTerm:当前节点最后一个日志的term周期blog
返回值:
term:接收投票节点的term值
voteGranted:是否投票给该申请节点索引
二、AppendEntries:Leader节点使用该消息向其余节点同步日志,而且发送空消息做为心跳包以维持Leader的统治地位。
请求参数:
term:此Leader所在的选举周期term值
leaderId:此Leader的节点编号
prevLogIndex:当前发送的日志的前面一个日志的索引
prevLogTerm:当前发送的日志的前面一个日志的term值
entries[]:须要个节点存储的日志序列
leaderCommit:此Leader已经提交给状态机的最大日志索引号
返回值:
term:接收日志节点的term值
success:若是接收日志节点的log[]结构中prevLogIndex索引处含有日志而且该日志的term等于prevLogTerm则返回true,不然false
三、InstallSnapshot:用来向节点安装快照的消息。快照可表明状态机依次应用了从0到第n个日志后的状态,发送快照可能会减小新节点的同步周期。
参数等请参考论文。
四、以上为论文中提到的RPC消息,除了这些,一个实际应用的集群还会有很对其余的消息,如:添加、删除节点,获取状态机状态等。大多都是Client与Leader的交互请求,能够根据须要在实现算法时添加。
在Raft集群启动时各个节点:
一、能够获取整个Raft集群的全部节点链接信息
二、currentTerm初始为0、votedFor初始为空
三、初始状态为Follower
四、若是是从新启动则有快照和日志序列,若是为新集群则所有为空
五、启动随机定时器,定时器超时时间在[m, n]范围内,保证请求传输时间 << [m, n] << 平均一个服务器两次出现宕机的时间间隔
一旦一个定时器超时,其会转换为Candidate状态。转换时执行:
一、重置随机定时器
二、currentTerm自增1
三、给本身投一票(votedFor设置为当前节点)
四、向全部其余节点发送RequestVote消息。
在Candidate状态下接收到RequestVote的返回结果:
一、获得了超过半数节点的赞成(voteGranted为true)则该节点当选Leader,转换本身的状态到Leader,并关闭随机定时器,周期发送AppendEntries给其余机器以保持领导地位。
二、不管是否已经成为Leader,接收到的RequestVote返回消息中的term > currentTerm则设置currentTerm = term,并将本身转换为Follower状态,并重置随机定时器。
三、定时器超时前没收到大多数投票则返回上述定时器超时逻辑开始下一轮投票。
在切换到Leader状态时,执行:
一、关闭随机定时器
二、将nextIndex[]中的值所有设置为本身最后一条日志的Index + 1
三、按期向其余机器发送AppendEntries(发送周期小于随机定时器最小超时时间,最好超时周期为发送周期的三倍左右):
(1)term等于此Leader的currentTerm
(2)leaderId等于此Leader的节点编号
(3)prevLogIndex等于nextIndex[]中该节点对应的值-1
(4)prevLogTerm等于此Leader的log[]中prevLogIndex对应日志的term值
(5)若是prevLogIndex+1不是此Leader的log[]中最后一条日志,则 entries[]取log[]中prevLogIndex以后紧接着的部分日志。
(6)leaderCommit等于此Leader的commitIndex
在Leader状态下接收到AppendEntries的返回结果:
一、返回消息中的term > currentTerm则设置currentTerm = term,并将本身转换为Follower状态,并重置随机定时器。
二、返回消息中success为false则将该节点在nextIndex[]对应的值减1
三、若是接收到大多数Follower的成功反馈,则能够提交该条AppendEntries所同步的全部日志,和此以前的全部日志。Leader只容许提交当前term的日志,不容许提交以前term的日志,可是能够经过提交当前term的日志达到间接提交以前term的日志的目的。
由于Leader不容许提交以前term的日志,所以在Leader被选举成功时能够发送一条无心义日志给其余机器,以更新日志列表中的最大term编号,当接收到大多数返回时提交该日志,以达到提交以前已被大多数节点接受的日志的目的
一个节点(不管当前是什么状态)接收到RequestVote(term, candidateId, lastLogIndex, lastLogTerm)消息,其会作以下判断(条件依次判断,不知足上一条才会进入下一条):
一、若是这条消息携带的term < currentTerm
则返回当前的term周期并拒绝投票请求:(currentTerm, false),并保持当前节点状态不变。
二、若是term == currentTerm
而且该节点的votedFor不为空而且不等于candidateId则返回当前的term周期并拒绝投票请求:(currentTerm, false),并保持当前节点状态不变,若是voteFor等于candidateId则给该节点返回投票:(currentTerm, true)并转换当前节点为Follower状态,并重置随机定时器。
三、若是term > currentTerm
则设置currentTerm = term, voteFor置为空并进入下一步。
四、若是该节点最后一条日志的term
> lastLogTerm
则返回当前的term周期并拒绝投票请求:(currentTerm, false),并保持当前节点状态不变。
五、若是该节点最后一条日志的term
== lastLogTerm
,则比较该节点最后一条日志的Index
和lastLogIndex
的大小。若是该节点最后一条日志的Index
> lastLogIndex
则返回当前的term周期并拒绝投票请求:(currentTerm, false),并保持当前节点状态不变。不然令votedFor = candidateId
给该节点返回投票:(currentTerm, true),并转换当前节点为Follower状态,并重置随机定时器。
六、若是该节点最后一条日志的term
< lastLogTerm
,令votedFor = candidateId
给该节点返回投票:(currentTerm, true)并转换当前节点为Follower状态,并重置随机定时器。
一个节点(不管当前是什么状态)接收到AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit)消息,其会作以下判断(条件依次判断,不知足上一条才会进入下一条):
一、若是这条消息携带的term < currentTerm
则返回当前的term周期并返回:(currentTerm, false),并保持当前节点状态不变。
二、若是term == currentTerm
,设置voteFor = leaderId。若是term > currentTerm
则设置currentTerm = term, voteFor = leaderId**。转换当前节点为Follower状态,重置随机定时器**并进入下一步。
三、若是当前节点log[]结构中prevLogIndex索引处含有日志而且该日志的term等于prevLogTerm则先执行如下日志存储而后返回(currentTerm, true),不然返回(currentTerm, false)
日志存储:
一、将prevLogIndex以后的日志所有删除,并将entries[]中的日志依次放入log[]中prevLogIndex以后的位置里。
二、若是leaderCommit(参数里)
> commitIndex(每一个节点存储里)
则设置commitIndex = leaderCommit并将(commitIndex, leaderCommit]区间的日志应用到状态机上(更新commitIndex后机器会自动应用该操做)。
公共要求:
一、全部节点在全部状态下,只要经过消息看到了消息中的term > currentTerm则设置currentTerm = term,并将本身转换为Follower状态,并重置随机定时器。
二、若是发现当前机器commitIndex > lastApplied则应该将本机log[]中序号为(lastApplied, commitIndex]的部分应用到状态机
参考资料: In Search of an Understandable Consensus Algorithm