首页 > 其他分享 >Raft协议及伪码解析

Raft协议及伪码解析

时间:2023-04-05 17:34:17浏览次数:36  
标签:leader term log candidate 伪码 follower Raft 解析 节点

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

节点的状态转换

首先需要明确,节点只有三种状态:

  • 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)


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

  • 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组成)发送给其他节点,开启计时器,进行选举。

选举时其他节点的视角


当其他节点收到了投票请求后,会首先对自身的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选举时的视角


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,取消定时器,终止选举。

消息如何广播复制


当应用发送消息给集群时,消息是如何被保存提交的?

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

重要的反复出现的ReplicateLog


在这里,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


这时有两种情况:

  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的一部分返回。

节点如何追加log,Appendentries


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

  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


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

总结:

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

标签:leader,term,log,candidate,伪码,follower,Raft,解析,节点
From: https://www.cnblogs.com/yuyinzi/p/17289121.html

相关文章

  • Spring 源码解析 - xml解析封装BeanDefinition(1)
    -  XML解析封装BeanDefinition  断点在 DefaultListableBeanFacy,registerBeanDefinition()二 如果给属性赋值 三 各种postprocessor       ##2、Spring套路点-1、AbstractBeanDefinition看看何时给容器中注入了什么组件-2、BeanFactory让......
  • 单机最快的队列Disruptor解析和使用
    前言介绍高性能队列Disruptor原理以及使用例子。Disruptor是什么?Disruptor是外汇和加密货币交易所运营商LMAXgroup建立高性能的金融交易所的结果。用于解决生产者、消费者及其数据存储的设计问题的高性能队列实现。可以对标JDK中的ArrayBlockingQueue。是目前单机且基于内......
  • kubegres 源码解析(二) kubebuilder简介
    摘要Kubegres完全使用KubebuilderV3版本开发,在对Kubegres进行代码解析前,首先了解一下Kubebuilder,本文尝试理清几个问题:如何使用Kubebuilder生成Controller/Operator项目?项目结构是什么,每个文件的作用是什么?具体到最重要的几个文件,代码如何组织,功......
  • 简单的CMakePresets.json解析 -- configurePresets
    ----CMake官方文档-----CMakeLists.txt是通用的c++项目管理文件,在不同的设备中,环境变量,编译器等都可能不同,将这些设置都交给CMakeLists.txt,并不是一个好办法。为了降低CMakeLists.txt的臃肿程度,简化其判断,可以针对不同设备,配置不同的CMakePresets.json.使得项目可以在......
  • Android 构建工具--AAPT2源码解析(一)
    一、什么是AAPT2在Android开发过程中,我们通过Gradle命令,启动一个构建任务,最终会生成构建产物“APK”文件。常规APK的构建流程如下:(引用自Google官方文档)编译所有的资源文件,生成资源表和R文件;编译Java文件并把class文件打包为dex文件;打包资源和dex文件,生成未签名的APK文件;签名APK生成......
  • SpringBoot 配置类解析
    作者:LiWanghongSpringBoot作为Java领域非常流行的开源框架,集成了大量常用的第三方库配置,SpringBoot应用中这些第三方库几乎可以是零配置的开箱即用,大部分的SpringBoot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。SpringBoot上手快,但是如果你的项目中业务场......
  • 解决适用EntityFramework生成时报错“无法解析依赖项。"EntityFramework 6.4.4" 与 '
    起因:通过vs2022创建mvc项目时,执行添加“包含视图的MVC5控制器(使用EntityFramework)时   点击添加,出现错误提示   解决方法:在您的解决方案资源管理器中,右键单击引用,管理nuget包,转到“已安装”选项卡并从EntityFramework.zh-Hans,卸载您的语言包,然后在重新添加......
  • swift tabview 带参数请求网络。多条目展示。json解析,逃逸闭包
    效果:用到的第三方#Uncommentthenextlinetodefineaglobalplatformforyourprojectplatform:ios,'9.0'target'News'do#Commentthenextlineifyoudon'twanttousedynamicframeworksuse_frameworks!#PodsforNewsp......
  • 【Python】ini解析ERROR:没有实例属性‘__getintem__’
    abaquspython搭配ini时,出现AttributeError:ConfigParserinstancehasnoattribute'getitem'20230404edit情况错误代码:fromConfigParserimportConfigParserconf=ConfigParser()conf.read(IniFilePath)layupFile=conf['DampCal']['lay......
  • 开启未来出行新模式,汽车以太网技术应用解析
    科技不断发展,汽车行业也在不断更新换代,越来越多的汽车开始应用以太网技术,实现智能化、网络化和信息化的升级。一、汽车以太网技术简介以太网技术是一种常见的局域网技术,可以实现高速数据传输。在汽车领域中,以太网技术被广泛应用于车载电子系统之间的通信和数据传输,例如车载娱乐......