Raft协议及伪码解析

跟着Martin大神学习Raft协议,带上讲解和伪码确实给人深入浅出的感觉,英音听起来十分优雅,也是一种享受了~
视频地址:Distributed Systems 6.2: Raft
整篇主要包括了十张Slide:

节点的状态转换

首先需要明确,节点只有三种状态:
Raft协议及伪码解析

  • follower
  • candidate
  • leader

follower

当一个节点刚启动的时候,或者刚从崩溃中恢复,它只会变成follower,等待来自其他节点的消息

candidate

follower有一段时间没有接收到来自leader或者candidate的消息,它会开始怀疑leader已经掉线,于是准备开始发起选举,推举自己变成leader

这段时间对于每个节点是随机设置的,否则所有节点刚启动的时候都会在同一时间发起选举。

如何选举?

概括一下:

  1. candidate会增加自己的term,然后邀请其他节点进行投票。
  2. 如果candidate接收到比自己更高的term(可能来自于leader或者其他candidate),那么它会重新变成follower
  3. 如果candidate收到足够多的投票,那么它会变成leader

下文都会使用quorum(合法的法定人数)来表示足够多的投票,表示过半数以上的节点。

  1. 如果candidate没有在计时器(timer)时间范围内获得quorum票数的话,选举超时。它会再次增加term,发起投票邀请,再次进入选举。

leader

当选了leader后,一般情况下会一直保持这个状态。除非:

  1. leader掉线了,那么它再次上线回重新变成follower
  2. 它接受到了来自其他节点发送的消息,而他们的term比它更高,那么它也会变成follower

这种情况可能是由于网络分区导致其他节点无法与它连接,认为它已经掉线而重新进行了选举。

伪码部分

节点初始化(Initialazation)

Raft协议及伪码解析
初始化中需要注意的变量有:
需要持久化的四个变量(他们的值不能因为节点崩溃而丢失)

  • currentTerm:节点当前处在的term
  • voteFor:节点把票投给的节点ID
  • log:可以认为是一个数组,每一个值entry包含了msgterm值。msg表示需要向其他节点广播复制的消息,而term表示该条消息所处的term的值。log通过追加新entry的方式进行增长,如果某条entry已经被quorum数量的节点复制成功,那么这条entry可以被commit,即被提交。
  • commitLength:已经被提交的长度
    其他变量可以因为节点崩溃在恢复时被重置。同时假设,每个节点拥有唯一的IDnodes变量保存了所有节点的ID,系统中的节点数量发生变化不在讨论的范畴之内。

前面提到,当follower发现不能与leader进行通信时,会使自己的currentTerm+1,然后将自己设置为candidate,发起选举。与此同时,它会将票投给自己,因此votedFor的值设置为自己的IDvotesReceived集合中也添加了自己的ID,并且初始化lastTerm

lastTerm代表节点本地log的最后一条entryterm值。

candidate将构造好的投票信息msg(由nodeId, currentTerm, log.length, lastTerm组成)发送给其他节点,开启计时器,进行选举。

选举时其他节点的视角

Raft协议及伪码解析
当其他节点收到了投票请求后,会首先对自身的currentTermcandidate发送过来的currentTerm(用cTerm替代)进行比较:

  1. 如果自身currentTerm小于cTerm,那么自己变成follower,更改自己的currentTerm变成cTerm
  2. 如果自身存在log,取出自己最新接收到的entryterm值作为自己的lastTerm
  3. logOk的值当candidate发来的lastTerm(用cLogTerm代替) > lastTerm(candidate的最新的logterm比自己大)或者cLogTerm=lastTerm 但是candidate.log.length >= log.length(candidate虽然与当前node中的最新logterm值相同,但是candidate拥有很多msg)true

由3可见,logOktrue的前提是,candidate拥有更新更多的log

  1. 当且仅当currentTerm接受了candidatecTerm,并且candidate拥有最新的log(logOktrue),且没有给其他candidate投票的情况下,可以将自身的voteFor的值设置为candidateID值。这种情况下,表示准备将票投给candidate,发送一条grantedtrueVoteResponse(由当前节点ID-nodeId, currentTerm, 是否投票给它的granted)消息返回给它。否则,返回一条grantedfalseVoteResponse消息。

回到candidate选举时的视角

Raft协议及伪码解析
candidate收到来自其他节点的回复时,判断:

  1. 如果自己仍然还是candidate,并且其他节点的term与自己的一致,也同意投票给自己,将这个节点添加至自己的votesReceived集合中(发起投票时,里面最初只有自己投给自己)。
  2. 判断votesReceived中节点的数量是否已经过半,如果已经过半了,那么将自己设置为leadercurrentLeader的值为自己的节点ID,并且取消计时器。遍历nodes集合中的所有node,更改sentLengthackLength字段,执行ReplicateLog函数。

sentLengthackLength都可以看做是一个mapkeyfollowernodeIdvalue是一个数值,代表长度。前者代表leader已经发送给某个followerlog长度,后者代表该follower已经确认收到的长度。显然,当sentLength的值设置为log.length,表明leader假设follower已经拥有和leader一样多的log,虽然这个假设可能是错的,但是我们会在后面进行修正。
ReplicateLog也会在后面进行解释。

  1. 如果节点的term大于自身的currentTerm,表明有比自己更高term的节点存在,那么自己重新变成follower,并且更改自己的currentTerm为那个更高的term,取消定时器,终止选举。

消息如何广播复制

Raft协议及伪码解析
当应用发送消息给集群时,消息是如何被保存提交的?

  1. 如果leader接收了消息,那么它可以将消息直接保存到自己的log中,然后修改自己的ackedLength,再遍历follower调用ReplicatedLog进行复制。
  2. 如果是follower接收到了消息,那么它需要通过FIFO队列,将这个请求发送给leader进行处理。
    与此同时,leader会周期性地向follower复制消息,即使没有新的消息需要进行广播,这样不仅可以充当与其他节点之间的心跳链接,也可以当做复制给某个follower失败时的重传。

重要的反复出现的ReplicateLog

Raft协议及伪码解析
在这里,sentLength派上了用场。利用sentLengthlog分割成两个部分:

  • prefixLen:已经发送给followerlog长度
  • suffix: 需要发送给followerlogentry列表。

如果prefixLen = log.length,那么suffix为空,代表没有新数据要发送给follower
leader构造了一个LogRequest,包括自身的ID, currentTermprefixLen, prefixTerm(已经发送给followerlog的最后一个值log[prefixLen-1]的term), commitLength, suffix发送给follower

节点收到了LogRequest

Raft协议及伪码解析
这时有两种情况:

  1. 节点可能正在处于candidate状态,如果接收到的term值大于currentTerm,那么它会取消选举,将自己变成follower,并设置leader,否则的话,返回一条接收失败的LogResponse给发送者。
  2. 节点是个普通的follower
    如果节点的状态是follower,那么需要判断logOk,这个值当且仅当当前节点的log长度大于等于prefixLen(当前节点的log不能小于leader认为的已经发送的长度,否则存在log的丢失),以及如果存在prefixLen,那么leader端的prefixTerm应当与当前节点的最新logterm一致。

Raft保证,如果两个节点的log在同样的index包含同样的term,那么他们在该index以及之前的log都是相同的。

如果节点的termleader一致,并且logOktrue,那么节点将会执行Appendentries,并且更改自己的ackprefixLen+suffix.length,作为LogResponse的一部分返回。

节点如何追加logAppendentries

Raft协议及伪码解析
follower需要判断的是,是否已经包含了这个消息。

  1. 如果followerlog长度大于prefixLen,并且suffix的长度不为空,意味着follower中的log可能包含了以前leader的消息,于是需要找到他们重叠位置的index
    根据这个index的值,判断当前logtermsuffix对应的term,如果不相等,意味着需要对当前的log进行截断,即丢弃prefixLen之后的log

为什么能在这个地方截断?因为进入Appendentries的前提是logOk已经为true,此时已经保证prefixLen之前的term已经一致。

  1. suffix的值追加到log
  2. 如果commitLength小于leadercommit的值,那么将自己没有提交的log发送给应用,然后增加自己的commitLength

再次回到leader, 如何处理LogResponse

Raft协议及伪码解析

  1. 如果leader发现返回的term大于自身的term,那么说明有新的leader,所以自身变为follower
  2. 否则,它会检查其他节点是否已经成功接收消息并且ack长度大于原来所记载的长度。
    2.1 如果是,那么更新sentLengthackLength,并且执行Commitlogentries
    2.2 successfalse,表明sentLength需要进行修正。然后重新执行Replicatelog

logOk不为true,可能是因为prefixLen >= log.length,也可能是因为prefixTerm != log[prefixLen-1].term

leader提交logCommitlogentries

Raft协议及伪码解析
leader遍历所有的log,将有超过半数以上被接收的entry,认为是已经readylog
找到最大下标的log,如果term与当前一直,意味着已经可以被认为commitlog长度增加了。并且这些消息已经被过半节点存储,可以发送给应用了。

总结:

  1. 每一个节点在收到比自己高的term时,会变成follower
  2. leader复制到其他节点时,发送LogRequest,其他节点返回LogResponse,用来标识是否已经成功复制。
  3. leader根据是否有过半的成功LogResponse来判断消息是否能够最终commit

原文链接:https://www.cnblogs.com/yuyinzi/p/17289121.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Raft协议及伪码解析 - Python技术站

(0)
上一篇 2023年4月17日
下一篇 2023年4月17日

相关文章

  • 什么是爬山算法?

    概念 爬山算法是一种局部择优的方法,是一种局部贪心的最优算法。 采用启发式方法,是对深度优先搜索的一种改进,它利用反馈信息帮助生成解的决策。 该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解,属于人工智能算法的一种。 主要思想 随机选择一个登山的起点; 每次拿相邻点与当前点进行比对,取两者中较优者,作为爬坡的下一步; 重复第2…

    Python问答区 2023年2月5日
    10
  • Java矢量队列Vector使用示例

    Java矢量队列Vector使用示例 Java中的Vector是一个可调整大小的数组实现,与ArrayList类似,但是可以支持同步。在需要线程安全时,可以使用Vector代替ArrayList。 Vector的创建 使用Vector需要先导入Java.util.Vector类,然后可以使用以下代码创建一个Vector: Vector<Object&g…

    数据结构 2023年5月17日
    00
  • C语言数据结构 双向链表的建立与基本操作

    C语言数据结构 双向链表的建立与基本操作 双向链表的定义 双向链表是一种常见的线性数据结构,它由多个结点组成,每个结点有两个指针,一个指向前一个结点,一个指向后一个结点。对于一个双向链表,我们可以获得其第一个结点和最后一个结点的指针,也可以沿着链表从前往后或从后往前遍历链表的每个结点。 双向链表的建立 我们首先需要定义一个双向链表的结点类型,包括两个指针,一…

    数据结构 2023年5月17日
    00
  • JavaScript 数据结构之集合创建(1)

    当我们在编写JavaScript程序时,有时需要使用数据结构来组织和表示数据。其中之一是集合,它是一组无序且唯一的项的集合。这里就介绍如何在JavaScript中创建集合。 1. 集合定义 集合是一种不同于数组或对象,由一组彼此无关的元素组成的数据结构。集合中的元素是唯一的,即不允许重复元素。 2. 集合的操作 JavaScript中的集合可以支持以下常见操…

    数据结构 2023年5月17日
    00
  • 带你了解Java数据结构和算法之递归

    带你了解Java数据结构和算法之递归 什么是递归? 递归是一种算法或计算机程序的设计方法,在程序执行过程中直接或间接的调用自身。 递归的实现方式 递归的实现通常使用函数进行的。在函数中,我们首先检查停止条件(递归基)是否满足,如果满足,我们停止递归;否则,我们调用自身递归进行下一步计算。 递归的应用场景 递归通常在解决问题中使用。对于像树、图等复杂结构的遍历…

    数据结构 2023年5月17日
    00
  • python深度学习人工智能BackPropagation链式法则

    Python深度学习人工智能BackPropagation链式法则 BackPropagation(反向传播)是深度学习中最常用的优化算法之一,它主要作用是通过代的方式,不断调整神经网络的权重和偏置,使得神经网络的损失函数最小化。本文将详细讲解BackPropagation的原理及Python实现,以及两个示例说明。 BackPropagation原理 Ba…

    python 2023年5月14日
    00
  • JavaScript数据结构yocto queue队列链表代码分析

    JavaScript数据结构yocto queue队列链表代码分析 什么是队列? 队列(Queue)是一种基础的数据结构,属于线性结构,它的特点是在队列尾插入元素,同时在队列头删除元素,遵循先进先出(FIFO)的原则。队列可以简单的理解为排队,先到达的先被服务,而后到达的则等在队列尾排队等待。队列的应用非常广泛,例如排队系统、消息队列等。 队列的实现方式 队…

    数据结构 2023年5月17日
    00
  • C语言链表详解及代码分析

    C语言链表详解及代码分析 简介 链表是一种常见的数据结构,它主要用于存储线性数据结构,可以动态地进行添加和删除操作。在C语言中,链表可以通过链式存储结构来实现。本篇攻略将详细讲解C语言链表的实现,包括定义链表、节点、添加节点、删除节点等操作。 链表的定义 链表由一个个节点组成,每个节点包含两个信息:数据和指向下一个节点的指针。在C语言中,可以通过结构体实现每…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部