分布式共识算法 (三) Raft算法

系列目录

分布式共识算法 (一) 背景

分布式共识算法 (二) Paxos算法

分布式共识算法 (三) Raft算法

分布式共识算法 (四) BTF算法

一、引子

1.1 介绍

Raft 是一种为了管理复制日志的一致性算法。它提供了和 Paxos 算法相同的功能和性能,但Raft更加容易理解和实践,在工程领域的重要性毋庸置疑。注:本文是在研读Raft算法论文后写出,因原版论文太长,故提炼了一下重点,方便大家快速掌握。

区别于一般一致性算法,Raft算法的特性如下:

  • 强Leader:Raft 使用一种更强的领导形式。日志只从Leader流向servers。这种方式简化了对复制日志的管理并且使得 Raft 算法更加易于理解。

  • Leader选举:Raft 算法使用一个随机计时器来选举Leader。这种方式只是在任何一致性算法都必须实现的心跳机制上增加了一点机制,这在解决冲突的时候会更加简单快捷。

  • 成员关系调整:Raft 使用一种共同一致的方法来处理集群成员变换的问题,处于调整过程中的两种不同的配置集群中大多数机器会有重叠,这就使得集群在成员变换的时候依然可以继续工作。

1.2 复制状态机(Replicated state machines)

  共识算法通常出现在复制状态机的上下文中。在这种方法中,一个server集群上的多个State Machine计算相同状态的相同副本,并且在一些机器宕掉的情况下也可以继续运行。复制状态机在分布式系统中被用于解决很多容错的问题。复制状态机通常都是基于复制日志实现的,结构如下图:

分布式共识算法 (三) Raft算法

如上图,每一个server存储一个包含一系列指令的log,并且State Machine顺序执行log中的指令。每一个Log都按照相同的顺序包含相同的指令,所以每一个server都执行相同的指令序列。因为每个状态机(state machines)都是确定的,每一次执行操作都产生相同的状态和同样的结果序列。

Replicated state machines 流程

  • 1. Client请求一致性模块(Consensus Module)。
  • 2.一致性模块(Consensus Module)接收Client发送来的指令然后追加进自己的Log中。它和其他server上的Consensus Module通信来保证每一个server上的Log最终都以相同的顺序包含相同的请求。
  • 3.一旦指令被正确的复制,每一个server的State Machine按照日志顺序处理他们。
  • 4.State Machine把结果返回给Client。

1.3 可理解性设计(Designing for understandability)

为了让算法更加可理解,Raft的作者做了2方面的努力:

  • 1.问题拆分:Raft 算法被分成*选举,日志复制,安全性和角色改变几个部分。
  • 2.状态简化:减少状态数量。

1.4 自学地址

二、Raft基础

2.1 Raft三种身份

raft算法中每个server可能存在3种身份:Leader领导者、Candidate候选者、Follower追随者。

分布式共识算法 (三) Raft算法

如上图,一个Follower没有收到来自Leader的心跳超时后,进入候选者Candidate身份,开始尝试参与选举。当收到超过1/2的选票时就变成Leader,并发送心跳给follower.

2.2 任期(term)

分布式共识算法 (三) Raft算法

如上图,Raft 把时间分割成任意长度的任期(term)。任期用连续的整数标记。每一段任期(term)从一次选举(election)开始,一个或者多个候选人尝试成为领导者。如果一个候选人赢得选举,然后他就在接下来的任期内充当*的职责。在某些情况下,会造成选票的割裂spilt(达不到大部分的票)。在这种情况下,这一任期会以没有*结束;一个新的任期(和一次新的选举)会很快重新开始。Raft 保证了在一个给定的任期内,最多只有一个领导者。

2.3 Raft 5个原则

Raft设定了5个原则,这些原则在任何时候有效!!

  • 选举安全:对于一个给定的任期号,最多只会有一个*被选举出来。
  • *只追加:*绝对不会删除或者覆盖自己的日志,只会追加新log entry。
  • 日志匹配:如果两个日志包含相同的index和term的log entry,那么这两个日志在index之前的entries完全相同。
  • *完整性:如果某个log entry在某个任期号中已经被提交,那么这个entry必然出现在更大任期号的所有*中。
  • 状态机安全:如果一个server已经把一个log entry应用到状态机的指定index中,那么不存在其他服务器在这个index提交一个不同的日志。(即相同index只存在相同的log)

三、Raft核心算法

Raft 将一致性问题分解成了三个相对独立的子问题:领导者选举(Leader election)、日志复制(Log replication)、安全性(Safety)。

3.1 领导者选举(Leader election)

Raft 使用一种"心跳机制"来触发*选举。当服务器程序启动时,他们都是跟随者身份。只要从*或者候选者处接收到有效的 RPCs,这个服务器节点保持着跟随者状态。Leader周期性的向所有Follower发送心跳包来维持自己的权威。如果一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,那么他就会认为系统中没有可用的领导者,并且发起选举以选出新的领导者。

要开始一次选举过程,Follower先要增加自己的当前任期(term)号并且转换到Candidate状态。然后他会并行的向集群中的其他服务器节点发送请求投票的 RPCs 来给自己投票。候选人会继续保持着当前状态直到以下三件事情之一发生:

  • (1) 赢得选举: 获得了大部分的针对同一个任期号的选票,那么他就赢得了这次选举并成为*。每一个服务器最多会对一个任期号投出一张选票,按照先来先服务的原则。成为*,会向其他的服务器发送心跳消息来建立自己的权威并且阻止新的*的产生。
  • (2) 其他的服务器成为领导者: 从其他的服务器接收到它是*的 追加日志项(AppendEntries) RPC。如果这个*的任期号(包含在此次的 RPC中)>= 候选人当前的任期号,那么候选人会承认*合法并回到跟随者状态。 如果此次 RPC 中的任期号比自己小,拒绝。
  • (3) 没有任何一个获胜: 如果有多个跟随者同时成为候选人,那么选票可能会被spilt以至于没有候选人可以赢得大多数人的支持。当这种情况发生的时候,每一个候选人都会超时,然后通过增加当前任期号来开始一轮新的选举。然而,没有其他机制的话,选票可能会被无限的重复瓜分。

  Raft 算法使用随机选举超时(例如 150-300 毫秒)的方法来确保很少会发生选票瓜分的情况,就算发生也能很快的解决。每一个候选人在开始选举时,会重置随机的选举超时时间,然后在超时时间内等待投票的结果;这样减少了在新的选举中另外的选票spilt的可能性。

3.2 日志复制(Log replication)

一旦一个Leader被选举出来,他就开始为客户端提供服务。客户端的每一个请求都包含一条被复制状态机执行的指令。leader把这条指令添加进log作为一个新entry,然后并行给其他的服务器发AppendEntries RPCs ,让他们复制这条entry。当这条log entry被安全的复制,leader会把entry添加进状态机中然后把执行的结果返回给client。如果follower崩溃或者运行缓慢,再或者网络丢包,leader会不断的重复尝试AppendEntries RPCs (尽管已经回复了客户端)直到所有的follower都最终存储了所有的log entry。

3.2.1 正常情况

分布式共识算法 (三) Raft算法

如上图,每一个log entry存储一条状态机指令和从Leader收到这条指令时的任期号(term number)。每一条log entry都有一个index来表明它在日志中的位置。

1.Leader一旦把创建的Log entry复制到大多数的server上的时候,log entry就会被提交commited(例如上图中的条目 7)。Raft 算法保证所有已提交的日志条目都是持久化的并且最终会被所有可用的状态机执行。

2.同时,*的日志中之前的所有日志条目也都会被提交,包括由其他*创建的条目。

3.一旦follower得知一个log entry已提交,follower就会把这个log entry添加进本地状态机

3.2.2 Leader崩溃

Leader崩溃后可能导致日志不一致,如下图:

分布式共识算法 (三) Raft算法

如上图,当一个*成功当选时,跟随者可能是任何情况(a-f)。每一个盒子表示是一个log entry;里面的数字表示任期号。跟随者可能会缺少一些log entry(a-b),可能会有一些未被提交的log entry(c-d),或者两种情况都存在(e-f)。例如,场景 f 可能会这样发生,某服务器在任期 2 的时候是*,已附加了一些log entry到自己的日志中,但在提交之前就崩溃了;很快这个机器就被重启了,在任期 3 重新被选为*,并且又增加了一些log entry到自己的日志中;在任期 2 和任期 3 的日志被提交之前,这个服务器又宕机了,并且在接下来的几个任期里一直处于宕机状态。

解决方案:

要使得follower的日志和自己一致,leader必须找到最后两者达成一致的地方,然后删除从那个点之后的所有log entry,发送自己的日志给follower。所有的这些操作都在进行AppendEntries RPCs 的一致性检查时完成。leader针对每一个跟随者维护了一个 nextIndex,这表示下一个需要发送给跟随者的log entry的index。当一个*刚获得权力的时候,他初始化所有的 nextIndex 值为自己的最后一条日志的index加1。如果一个跟随者的日志和*不一致,那么在下一次的附加日志 RPC 时的一致性检查就会失败。在被跟随者拒绝之后,*就会减小 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得*和跟随者的日志达成一致。当这种情况发生,AppendEntries RPC 就会成功,这时就会把跟随者冲突的日志条目全部删除并且加上*的日志。一旦AppendEntries  RPC 成功,那么跟随者的日志就会和*保持一致,并且在接下来的任期里一直继续保持。

3.3 安全性(Safety)

3.3.1. 选举限制(Election restriction)

Raft使用voting process来防止那些log不包含全部committed entry的candidate赢得选举。candidate为了赢得选举必须和cluster的majority进行交互,这意味着每个committed entry必须都在其中的一个majority存在。如果一个candidate的log至少和任何majority中的log保持up-to-date("up-to-date"将在下文精确定义),那么它就包含了所有committed entry。RequestVote RPC实现了这一约束:RPC中包含了candidate的log信息,如果它自己的log比该candidate的log更新,voter会拒绝投票。

如何比较哪个新(up-to-date)?

Raft通过比较log中last entry的index和term来确定两个log哪个更up-to-date。如果两个log的last entry有不同的term,那么拥有较大term的那个log更up-to-date。如果两个log以相同的term结束,那么哪个log更长就更up-to-date。

3.3.2. 提交前任期的日志条目(Committing entries from previous terms)

分布式共识算法 (三) Raft算法

如上图,展示了为什么*无法决定对老任期号的日志条目进行提交。在 (a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。在 (b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。然后到 (c),S5 又崩溃了;S1 重新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,但是还没有被提交。如果 S1 在 (d) 中又崩溃了,S5 可以重新被选举成功(通过来自 S2,S3 和 S4 的选票),然后覆盖了他们在索引 2 处的日志。反之,如果在崩溃之前,S1 把自己主导的新任期里产生的日志条目复制到了大多数机器上,就如 (e) 中那样,那么在后面任期里面这些新的日志条目就会被提交(因为S5 就不可能选举成功)。 这样在同一时刻就同时保证了,之前的所有老的日志条目就会被提交。

解决方案:

为了防止这样问题的发生,Raft不会通过计算备份的数目,来提交之前term的log entry。只有leader的当前term的log entry才会计算备份数并committed;一旦当前term的entry以这种方式被committed了,那么之前的所有entry都将因为Log Matching Property而被间接committed。

3.3.3 安全性论证(Safety argument)

这里原论文是对Raft5个原则的第四个“领导者完整性(Leader Completeness Property)”的论证,我们并不关心这个。

3.4 跟随者和候选人崩溃(Follower and candidate crashes

如果跟随者或者候选人崩溃了,那么后续发送给他们的 RPCs 都会失败。Raft 就会无限的重试;如果崩溃的机器重启了,就成功了。Raft 的 RPCs 都是幂等的,所以这样重试不会造成任何问题。例如一个跟随者如果收到附加日志请求但是他已经包含了这一日志,那么他就会直接忽略这个新的请求。

3.5 时间和可用性(Timing and availability

  我们对于Raft的一个要求是,它的安全性不能依赖于时间:系统不会因为有些事件发生地比预期慢了或快了而产生错误的结果。然而,可用性(系统及时响应client的能力)将不可避免地依赖于时间。比如,因为server崩溃造成的信息交换的时间比通常情况下来得长,candidate就不能停留足够长的时间来赢得election;而没有一个稳定的leader,Raft将不能进一步执行。

leader election是Raft中时间起最重要作用的地方。当系统满足以下的timing requirement的时候,Raft就能够选举并且维护一个稳定的leader:

广播时间(broadcastTime) << 选举超时时间(electionTimeout) << 平均故障间隔时间(MTBF)

在这个不等式中,broadcastTime是server并行地向集群中的每个server发送RPC并且收到回复的平均时间;electionTimeout就是选举超时;MTBF是单个server发生故障的时间间隔。broadcastTime必须比electionTimeout小几个数量级,这样leader就能可靠地发送heartbeat message从而防止follower开始选举;通过随机化的方法确定electionTimeout,该不等式又让split vote不太可能出现。electionTimeout必须比MTBF小几个数量级,从而让系统能稳定运行。当leader崩溃时,系统会在大概一个electionTimeout里不可用;我们希望这只占整个时间的很小一部分。

broadcastTime和MTBF都是底层系统的特性,而electionTimeout是我们必须选择的。Raft的RPC通常要求接收者持久化信息到stable storage,因此broadcastTime的范围在0.5ms到20ms之间,这取决于存储技术。因此,electionTimeout可以取10ms到500ms。通常,server的MTBF是几个月或者更多,因此很容易满足timing requirement。

四、总结

Raft 和 Paxos 最大的不同之处就在于 Raft 的强领导特性:Raft 使用*选举作为一致性协议里必不可少的部分,并且将尽可能多的功能集中到了*身上。这样就可以使得算法更加容易理解。目前Raft已应用到了 腾讯云消息队列(Cloud Message Queue,CMQ)上。

============参考===========

论文:In Search of an Understandable Consensus Algorithm (Extended Version)

博客:

https://blog.csdn.net/qq_36561697/article/details/88550398

https://www.cnblogs.com/YaoDD/p/6172011.html

一文搞懂Raft算法

上一篇:文本比较算法三——SUNDAY 算法


下一篇:VSTO - 使用Excel加载项生成表和图表