Part 15:Raft论文翻译-《CONSENSUS BRIDGING THEORY AND PRACTICE》(集群成员变更-可用性)
4.2 Availability(可用性)
群集成员身份的变更在保留集群的可用性方面引入了几个问题。第4.2.1节讨论了在将新服务器添加到群集之前赶上新服务器,以便它们不会阻止新日志项的承诺;第4.2.2节讨论了如果现有Leader从群集中删除时如何逐步淘汰;第4.2.3节描述了如何防止删除的服务器中断新群集的Leader工作。最后,第4.2.4节的结尾是,为什么Raft的成员更改算法足以在任何成员更改期间保持可用性。
4.2.1 Catching up new servers (追加配置log到新server)
当服务器添加到集群时,它通常不会存储任何log entry。如果在此状态下添加到集群中,其日志可能需要相当长的时间才能赶上Leader的日志,在此期间,集群更容易不可用。例如,一个包含三台服务器的集群通常可以容忍一个故障,而不会损失可用性。但是,如果将日志为空的第四台服务器添加到同一集群,并且原始三个服务器中的一台失败,则集群将暂时无法提交新的log entry(见图4.4(a))。如果快速连续将许多新服务器添加到集群中,则可能会出现另一个可用性问题,其中需要新服务器才能形成集群的大多数(参见图4.4(b))。在这两种情况下,在新服务器的日志追到领导者的日志之前,集群将不可用。
为了避免可用性差距,Raft在配置更改之前引入了一个附加的阶段,其中一个新的服务器作为一个无投票的成员加入集群。Leader将日志复制到其log entry,但为了投票或承诺的目的,它尚未计入多数。一旦新服务器赶上集群的其他部分,就可以按照上所述进行重新配置。(支持无投票服务器的机制在其他上下文下也可能很有用;例如,它可以用于将状态复制到大量服务器上,这些服务器可以轻松地一致性地服务只读请求。)
Leader需要确定新服务器何时足够赶上log以继续进行配置更改。这需要一些注意事项,以保持可用性:如果服务器添加得太快,那么集群的可用性可能会面临风险,如上所述。我们的目标是将任何临时不可用时间保持在选举超时时间以内(就是不可用时间不会超过超时时间),因为客户必须已经能够容忍如此大规模的临时不可用时间(在Leader失败的情况下)。此外,如果可能的话,我们希望通过使新服务器的日志更接近领导者的日志来进一步减少不可用性。
如果新服务器不可用或日志追赶的速度太慢,永远无法赶上,Leader还应该中止更改。这一检查很重要:Lamport的Paxos算法在此情况下是不可用的,因为Paxos算法没有包括日志追赶的检查功能。尝试添加不可用或速度较慢的服务器通常是错误的。
我们建议以下算法来确定新服务器何时已经追赶上合适的log并足以添加到集群。将log entry复制到新服务器时,将被分成几轮,如图4.5所示。每一轮复制都复制如图4.5所示的一轮范围内的log entry。当server它正在复制当前一轮的log entry时,新的log entry可能会到达Leader;这些新的log entry将在下一轮中复制追加这些内容。随着复制的进行,每一轮复制的时间将逐渐减少。该算法将等待固定数量的复制轮数(例如10轮)。如果最后一轮持续时间小于选举超时,则领导者将新服务器添加到集群中,这是基于这样的假设:没有足够的未复制log entry足以导致不可用时间超过超时时间。否则,Leader将终止成员变更并报错。调用者可能一直重试(下次更有可能成功,因为新服务器已经追赶了部分log entry)。
作为日志追赶的第一步,Leader必须发现新服务器的日志为空。对于一个新的服务器,AppendEntries RPC的一致性检查将反复失败,直到Leader发现该server的log为空(即Leader通过RPC的日志检查发现该server的nextIndex=1)。这种基于AppendEntries RPC的一致性检查操作可能是向集群添加新服务器而导致性能下降的主要因素(在此阶段之后,可以通过使用批处理将日志项以较少的rpc传输到Follower)(也就是说,AppendEntries RPC对server的nextIndex的检查比较耗时,第三章也说明了一些优化方案,比如按照Term进行检查)。同时,也有其它的方法可以使该步骤的时间变短,包括在第3章中描述的那些方法。然而,解决添加新服务器这个特殊时间性能问题的最简单方法是让Follower在AppendEntries RPC响应中返回其日志的长度,这将使得Leader能够根据这个长度快速的确定server的nextIndex的值,从而加速该进程。
4.2.2 Removing the current leader(删除当前Leader)
如果当前的Leader被要求从现有的集群中剔除,那么这个Leader就要在未来的某个时间点下线。最直接的方式就是利用第3章所说的Leader角色转换的方法来实现:被要求下线的Leader将Leader角色转给其它的服务器,然后变成Leader的服务器执行正常的集群成员变更流程。
我们最初为Raft开发了一种不同的方法,即现有的Leader进行成员变更以移除自己,然后它退出。这使得Raft处于一种有点尴尬的操作模式,Leader会暂时管理它不是成员之一的配置。我们最初需要这种方法来进行任意的配置更改(见第4.3节),其中旧的配置和新的配置可能没有任何可以转Leader的共同服务器(也就是任意的数量的成员加入/删除)。同样的方法也适用于不实施Leader转换的系统。
在这个方法里,Leader在需要在Cnew成功提交后才能退出。如果Leader在Cnew成功提交前退出,系统中任然可能存在Leader超时而且这个Leader在此被选为Leader的可能性。例如,在 一个只有两个服务器的集群中删除当前的Leader这个极端的场景中,这个Leader可能不得不再次变成Leader以保证集群的正常运转,如图4.6所示。这样,Leader必须等到Cnew被提交后才能退出。这时第一点,新的配置能够明确的在没有已退出的Leader的参与下正常运行:集群能够从新配置Cnew中选出新的Leader,且这个Leader来自Cnew。在Leader退出后,新集群中的某个服务器将会超时(与Leader的心跳超时)并获得新一轮的Leader选举成为新Leader。在新老Leader交替时系统不可用应该能够被容忍,因为同样的不可用也会来自Leader的失效。
这种方法对决策的两个影响不是特别有害,但可能令人惊讶。首先,将有一段时间(在提交Cnew时),Leader可以管理不包含自己在内的集群;它复制log entry,但是在统计多数派的时候不会把自己算进去。其次,某个服务器不在自己的最新配置中仍然启动新选举,因为在提交Cnew之前,可能仍然需要它(如图4.6所示)。这个服务器在选票统计的时候不会统计自己的投票,除非它出现在Cnew中。(也就是说,如果这个服务器是将要被提出的服务器,这个服务器自己的配置信息中也反映出它要被剔除的消息,那么即使这个服务器发起了选举,它自己的那个赞成票也不会被统计)。
4.2.3 Disruptive servers(中断服务器)
如果没有合适的处理机制的话,那么不在新的配置信息(即Cnew)中的服务器可能会扰乱集群的运行。一旦Leader创建了一个新的集群配置信息(即集群中服务器的清单,即Cnew)的log entry,某个不在Cnew中的服务器将接收不到来自Leader的心跳,因此,这个服务器将会超时并发起新一轮的Leader选举。进一步,它接收不到Cnew,也不会知道新log entry的提交信息,它自己都不知道自己被踢出了集群。那么,这个服务器将会发送RequestVote RPC,携带新的term编号,这将导致新的Leader变成Follower(其实这个服务器永不会被选为Leader)。系统中最终肯定会在Cnew中选出一个新的Leader,但是这个服务器会不断的发送RequestVote RPC,以至于系统的可用性和性能降低,如果有多个被剔除的服务器,这种情况将更加严重。
我们第一个处理方法是,一个服务器在发起Leader选举前需要判断自己是否会浪费其它服务器的时间,也就是自己是不是有机会成为Leader。这就在Leader选举算法中引入了一个新的步骤,称为“Pre-Vote phase”预选举阶段。这个服务器在发起选举请求前先向其它的服务器询问:自己的log是不是最新的,只有这个服务器确信自己的log是最新的(从大部分的其它服务器处获得肯定的回复),它才会真正的发起Leader 选举。
不幸的是,Pre-Vote phase仍然没有解决上述问题:还是会存在这个服务器的log是新的,但是还是会扰乱正常集群运行的情况。更令人惊奇的是,这种情况即使在配置信息变更完成的情况下也可能发生。如图4.7所示,在S4成为新Leader并且在自己的log entry已经提交了Cnew后,S1仍然可能成为新的Leader(在S4同步y<-1和Cnew给S2、S3之前,S1可能获得S2、S3的赞成票)。
第一个方法没法解决上述问题,我们给出了第二的方法。
第二个方法:Raft的解决方案是使用心跳来判断集群中是否真的需要新的Leader。在Raft中,如果Leader能够保持对Follower的心跳,那个这个Leader就被认为是活跃的(否则,另一个服务器将开始选举)。因此,任一个服务器不应该能够破坏集群正在接收心跳的Leader(也就是说,如果系统中的当前Leader能够很好的工作,那就没有必要新选Leader,这个判断来自于心跳是否正常)。我们修改了RequestVote RPC以实现这一点:如果某个服务器在接收到RequestVote RPC后且在超时时间之内收到了当前Leader的心跳消息,那么它就不会给Candidate投票。
第二个方法以第三章说的新Leader选举机制有点冲突(第三章中的算法表示:在回复RequestVote RPC时不需要等待一个超时时间),这种情况下,RequestVote RPC是应该被处理的,即使系统中有正常的Leader存在。也就是说,第三章的方法允许当系统中有正常Leader存在时进行新Leader选举,而本章又不允许当系统中有正常Leader存在时进行新Leader选举。解决方法就是:给RequestVote RPC加一个标识,如果真需要在正常Leader存在时进行新Leader选举,那就携带这个标志,以表示我就是要发起新选举,接收到RequestVote RPC的服务器看到这个标志后,就放弃了对Leader是否正常的约束,这需要大家在系统中约定好这个标志位。
4.2.4 Availability argument(可用性说明/证明)
本节将证明上述解决方案足以在集群成员变更期间保持可用性。由于Raft的成员变更是基于Leader的,我们表明该算法将能够在成员更改期间维护和替换Leader,并且Leader将同时服务客户请求并完成配置更改。我们假设大多数旧配置是可用的(至少在提交Cnew之前),并且大多数新配置是可用的。(这里的配置指的是配置中的服务器的清单列出的服务器节点)
(1)在配置变更的任何步骤中Leader都可以被选出
- 如果新集群中的某个服务器(拥有最新的log)复制了Cnew这个log entry,那么这个服务器能够在新集群中获得多数派的选票而成为Leader;
- 否则,说明Cnew这个log entry还没有被新集群的大多数服务器所复制,也就是这个log entry还没有被提交。那么在新集群Cnew或者旧群Cold的某个服务器一定能够获得多数派选票成为Leader。(也就是新Leader可能来自新集群或者旧集群)
(2)一个Leader一旦当选就会保持不变,假设心跳能够传输到它自己的配置,除非这个Leader是被明确的被剔除因为它不在Cnew
- 如果一个Leader能够可靠的给自己配置集群中的服务器发送心跳信息,那么它自己以及集群中其它的服务器不会接受新的Term:他们不会超时以发起新的选举,而且他们会忽略其它的选举请求,这样这个Leader就不会被迫下线;
- 如果一个Leader不在Cnew中且已经提交了Cnew并让位了,Raft会选举一个新的Leader。新的Leader会是来自Cnew,以保证配置变更能够继续。然而,退出让位的Leader可能还会被选为新Leader(存在这样的风险)。如果这个Leader又被选为Leader,那么它会确认Cnew已经提交并再次退位,Cnew中的其它服务器应该会在下一次中胜选。
(3)Leader将在整个配置更改过程中为客户端请求提供服务
- Leader可以在整个更改过程中继续将客户端请求追加到其日志中;
- 由于新服务器在添加到集群之前已经被追赶了log,因此Leader可以快速的进行log 复制及时回复客户端。(也就是说新加入集群的服务器进行日志追赶的时间不会太长,不会因为日志追赶而拖慢Leader响应客户端的请求)
(4)Leader将通过提交Cnew来保证程序的正常运行并完成集群配置变更,而且,如果有必要,该Leader退位以允许Cnew的某个服务器被选为新Leader。
因此,在上述假设下,本节中描述的机制足以在任何成员更改期间保持可用性。
<< 上一章:集群成员变更-安全性
下一章:集群成员变更-通过联合共识支持任意的配置变更 >>