Raft协议简析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Raft协议简析相关的知识,希望对你有一定的参考价值。

参考技术A 一致性算法之所以可以保证在有节点挂掉时也能够继续服务, 就是因为有Replicated state machines的存在。 在分布式系统中, 有两种方式来实现这个复制状态机

一致性算法一般有一下特点:

Raft算法首先会选举一个leader, 然后又leader来管理replicated log。 leader会从client处接收请求, 然后转发给别的节点, 并且告诉这些节点什么时候可以把这些请求应用在状态机上(落地)。 当一个leader挂了的时候, 会马上选举出一个新的leader。 由上可知, Raft将一致性问题拆分成了3个独立的子问题:

Logs 组织的形式如图所示。 当leader收到一个Log entry时, 每个log entry都会存储一个带有term number的state machine命令。 Term number是用来探测log之间的不一致性并且保证Figure3的一些特性。 每个log也还带有他们在log里的索引数字。

当leader认为这个entry是可以成功应用到状态机上, 那么这个entry就被成为commited。Raft保证所有commited entry最终都会被应用到所有的节点上。

当一个entry被成功复制到半数以上的节点后, 这个log就可以认为成功写入了。并且会将leader之前的写入也视为commit。

设计Raft日志机制不仅简化了系统的行为, 也保证了正确性。 保证了Figure3的以下特性
1. 当不同log中的两个entry有相同的term和index, 那么这两个entry就是相同的
2. 当不同log中的两个entry有相同的term和index, 那么这两个entry之前的所有entry也都是相同的

当leader发现follower的log跟自己的不同时, 他会针对每个follower维护一个nextIndex。 这个nextIndex就是Leader下次会发第几个entry给这个follower。 而follower也会拒绝这个append entry的rpc。

Safety

如Figure 4所示, 如果term T commit的数据在之后的term U丢失了, 那么
1. 这个log一定不在Leader-U的log中(因为leader不会覆盖旧数据)
2. Leader-T把这个日志复制给了大多数节点并且Leader-U收到了大多数节点的投票, 所以至少有一个节点是既收到了这个entry并且又给Leader-U投票了
3. 那么这个投票的节点也一定收到了所有Leader-T的commited entries
4. 因为这个投票节点把票投给了U, 那么说明Leader-U也至少有所有这个节点的entries
由此可知, 如果投票节点和Leader-U 共享了之前的log, 那么Leader-U肯定会有所有投票节点的entry。 另外, Leader-U的last-log-term必须比T大, 而投票节点的term至少是T。之前term的leader在复制entry给Leader-U时也必须包含了之前的这个entry。 投票节点和之前term的leader都会有这个entry, 所以Leader-U也不会没有这个entry。可下结论所有大于T的term的leader绝对包含了所有term T commited的entries。

raft协议的一些要点

raft 协议是一致性协议中相对容易理解的一个实现,类似的还有zab,paxos等一致性算法。etcd是基于raft协议的,k8s依赖于etcd,下面介绍下raft协议的一些要点


1,Raft算法的节点数量问题

Raft协议中主节点心跳失效后follower成为candidate并且在n/2个节点投票成为主节点,在RAFT协议集群中如何确认n是多少?动态增加机器如何变化n,超时多久应该认为节点为n-1?n固定好是动态调整好?


领导人选举、日志复制、安全性的讨论都是基于Raft集群成员恒定不变的,然而在很多时候,集群的节点可能需要进行维护,或者是因为需要扩容,那么就难以避免的需要向Raft集群中添加和删除节点。最简单的方式就是停止整个集群,更改集群的静态配置,然后重新启动集群,但是这样就丧失了集群的可用性,往往是不可取的,所以Raft提供了两种在不停机的情况下,动态的更改集群成员的方式:


单节点成员变更:One Server ConfChange

多节点联合共识:Joint Consensus

从Cold迁移到Cnew的过程中,因为各个节点收到最新配置的实际不一样,那么肯能导致在同一任期下多个Leader同时存在。


为了解决上面的问题,在集群成员变更的时候需要作出一些限定。


单节点成员变更

所谓单节点成员变更,就是每次只想集群中添加或移除一个节点。比如说以前集群中存在三个节点,现在需要将集群拓展为五个节点,那么就需要一个一个节点的添加,而不是一次添加两个节点。


这个为什么安全呢?很容易枚举出所有情况,原有集群奇偶数节点情况下,分别添加和删除一个节点。在下图中可以看出,如果每次只增加和删除一个节点,那么Cold的Majority和Cnew的Majority之间一定存在交集,也就说是在同一个Term中,Cold和Cnew中交集的那一个节点只会进行一次投票,要么投票给Cold,要么投票给Cnew,这样就避免了同一Term下出现两个Leader。


变更的流程如下:


Leader在收到请求以后,回向日志中追加一条ConfChange的日志,其中包含了Cnew,后续这些日志会随着AppendEntries的RPC同步所有的Follower节点中

当ConfChange的日志被添加到日志中是立即生效(注意:不是等到提交以后才生效)

当ConfChange的日志被复制到Cnew的Majority服务器上时,那么就可以对日志进行提交了

以上就是整个单节点的变更流程,在日志被提交以后,那么就可以:


马上响应客户端,变更已经完成

如果变更过程中移除了服务器,那么服务器可以关机了

可以开始下一轮的成员变更了,注意在上一次变更没有结束之前,是不允许开始下一次变更的

可用性

可用性问题

在我们向集群添加或者删除一个节点以后,可能会导致服务的不可用,比如向一个有三个节点的集群中添加一个干净的,没有任何日志的新节点,在添加节点以后,原集群中的一个Follower宕机了,那么此时集群中还有三个节点可用,满足Majority,但是因为其中新加入的节点是干净的,没有任何日志的节点,需要花时间追赶最新的日志,所以在新节点追赶日志期间,整个服务是不可用的。


2,客户端交互

包括客户端如何发现领导人和 Raft 是如何支持线性化语义的


我们 Raft 的目标是要实现线性化语义(每一次操作立即执行,只执行一次,在他调用和收到回复之间)。但是,如上述,Raft 是可以执行同一条命令多次的:例如,如果领导人在提交了这条日志之后,但是在响应客户端之前崩溃了,那么客户端会和新的领导人重试这条指令,导致这条命令就被再次执行了。解决方案就是客户端对于每一条指令都赋予一个唯一的序列号。然后,状态机跟踪每条指令最新的序列号和相应的响应。如果接收到一条指令,它的序列号已经被执行了,那么就立即返回结果,而不重新执行指令。


3,领导人选举

Raft中的节点有三种状态:


领导人状态:Leader

跟随者状态:Follower

候选人状态:Candidate

每一个节点都是一个状态机,Raft会根据当前的心跳,任期等状态来进行状态的迁移转化


首先,在Raft节点启动的时候,所有任务都是Follower状态, 因为此时没有Leader,所有Follower都在固定的超时时间内都收不到来自Leader的心跳,从而变成了Candidate状态,开始选举Leader


当节点处于Candidate状态的时候,会并发的向所有的节点发出请求投票请求RequestVote(后面章节会向详细介绍),在Candidate状态下,节点可能会发生三种状态的迁移变化:


开始下一轮新的选举:发出的投票请求在固定时间内没有收到其他节点的响应,或者是收到响应(或者同意投票)的节点数量没有达到 N/2+1,那么选取超时,进入下一轮选举

选举成功,成为新的Leader:如果选举过程中收到大于N/2+1数量的节点的投票,那么选举成功,当前的节点成为新的Leader

成为Follower:如果选举过程中收到来及其他节点的Leader心跳,或者是请求投票响应的Term大于当前的节点Term,那么说明有新任期的Leader

如果节点选举成功,成为了Leader,那么Leader将会在固定周期内发送心跳到所有的节点,但是如果心跳请求收到的响应的Term大于当前节点的Term,那么当前节点的就会成为Follower。比如Leader节点的网络不稳定,掉线了一段时间,网络恢复的时候,肯定有其他节点被选为了新的Leader,但是当前节点在掉线的时候并没有发现其他节点选为Leader,仍然发送心跳给其他节点,其他节点就会把当前的新的Term响应给已经过时的Leader,从而转变成Follower


4,日志复制

领导人必须从客户端接收日志然后复制到集群中的其他节点,并且强制要求其他节点的日志保持和自己相同。


复制状态机通常都是基于复制日志实现的,每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。


客户端请求服务器,请求的信息就是一系列的指明,比如PUT KEY VALUE

服务器在收到请求以后,将操作指令同步到所有的服务器中

服务器收到同步的指令以后,就将指令应用到状态机中

最后响应客户端操作成功


5,节点超时机制

每个节点都有150~300ms的随机超时,如果收到leader的心跳包,会重新计时,否则将自己状态设置为candidate,发起投票。由于时间是随机的,减少了同时发起投票的可能性。follower是通过节点超时机制知道leader存活的,否则可能认为leader已经死亡。


以上是关于Raft协议简析的主要内容,如果未能解决你的问题,请参考以下文章

Raft协议

raft协议的一些要点

raft 和 zab协议

聊聊分布式一致性协议---Raft协议

Raft协议处理各种failover情况

raft协议