理解Raft一致性算法—一篇学术论文总结
https://mp.weixin.qq.com/s/RkMeYyUck1WQPjNiGvahKQ
作者: Shubheksha
译者: java达人
来源: https://www.freecodecamp.org/news/in-search-of-an-understandable-consensus-algorithm-a-summary-4bc294c97e0d/
本文总结了Diego Ongaro和John Ousterhout在 In Search of An Understandable Consensus Algorithmby 提出的Raft一致性算法。所有的引言都取自那篇论文。(https://www.usenix.org/system/files/conference/atc14/atc14-paper-ongaro.pdf)
Raft:
Raft是一种分布式一致性算法。它被设计得易于理解, 解决了即使在出现故障时也可以让多个服务器对共享状态达成一致的问题。共享状态通常是通过日志复制支持的数据结构。只要大多数服务器是正常运作的,系统就能全面运行。Raft的工作方式是在集群中选举一个领导者。领导者负责接受客户端请求并管理到其他服务器的日志复制。数据只在一个方向流动:从领导者到其他服务器。
Raft将一致性问题分解为三个子问题:
- 领导者选举: 现有领导者失效时,需要选举新的领导者。
- 日志复制: 领导者需要通过复制保持所有服务器的日志与自己的同步。
- 安全性: 如果其中一个服务器在特定索引上提交了日志条目,那么其他服务器不能在该索引应用不同的日志条目。
选举安全:在一次任期内最多只有一个领导者被选出leader 只添加操作:领导者在其日志中只添加新条目,不覆盖删除条目日志匹配:如果两个log包含拥有相同索引和任期的条目,那么这两个log从之前到给定索引处的所有日志条目都是相同的。leader完整性:如果在给定的任期中提交了一个日志条目,那么该条目将出现在所有后续更高任期号的领导者的日志中。状态机安全性:如果服务器已将给定索引处的日志条目应用于其状态机,则其他服务器不能在该索引位置应用别的日志条目Raft确保这些属性一直为true。
基础
每个服务器存在状态有以下三种:领导者、追随者或候选人。
服务器状态变化
在正常操作中,只有一个领导者,其他所有服务器都是追随者。追随者是被动的:他们不会主动发出请求,而只是响应领导者和候选人的请求。领导者处理所有客户端请求(如果客户端与追随者联系,追随者将其重定向到领导者)。第三种状态候选人,在选举新的领导者时被使用。
Raft将时间划分为任意长度的任期,每个任期都以一次选举开始。如果一名候选人赢得选举,他在剩下的任期时间内仍然是领导者。如果投票出现分歧,那么这个任期则没有领导者,及时结束。任期号单调递增。每个服务器存储当前任期号,并在每次通信中交换该任期编号。
如果一个服务器的当前任期号小于其他服务器,那么它将把当前任期更新为更大的值。如果候选人或领导者发现其任期已过期,则立即转化为追随者状态。如果服务器接收到带有过期任期号的请求,它将拒绝该请求。
Raft利用两类远程过程调用(rpc)来执行其基本操作。
- 候选人在选举期间使用RequestVote
- AppendEntry被领导者用来复制日志条目,也用作心跳(检查服务器是否启动的信号—它不包含任何日志条目)
领导者选举
领导者定期向追随者发送心跳,以维持权限。当追随者在等待领导者的心跳超时时,将触发领导者选举。从追随者转换到候选人状态,并增加其任期号。在为自己投票之后,它会向集群中的其他成员并行地发出RequestVotes RPC。这有三种可能的结果:
- 候选人从大多数服务器获得选票并成为领导者。然后,它向集群中的其他成员发送心跳消息以建立权限。
- 如果其他候选人收到AppendEntries RPC,他们检查任期编号。如果任期大于他们自己的,他们接受该服务器为领导者并返回到追随者状态。如果任期号更小,则它们拒绝该RPC,保持候选人状态。
- 候选人中没有选出领导者。如果同时超过一台服务器成为候选人,由于没有明显的多数票,投票可能会出现分歧。在这种情况下,其中一名候选人超时后, 新的选举开始。
Raft通过随机的选举超时时间来确保投票分歧是罕见的,即使出现了它们也能够快速解决。为了从一开始就防止投票分歧,选举超时时间是从固定的间隔中随机选择的(例如,150-300ms)。它被分发到服务器,因此在大多数情况下,只有一台服务器会超时;它赢得了选举,并在其他服务器超时之前发送心跳。同样的机制也用于处理投票分歧。每个候选人在选举开始时重启其随机的选举超时计数,并在开始下一次选举前等待超时;这降低了在新选举中再次出现投票分歧的可能性。
日志复制:
现在假定客户端是只写请求。每个请求都包含一个命令,理想情况下由所有服务器的复制状态机执行。当一个领导者收到一个客户端请求时,将它作为一个新的条目添加到自己的日志中。日志中的每一条目如下:
- 包含客户端指定的命令
- 有一个索引来标识日志中条目的位置(索引从1开始)
- 有一个任期编号,用于在逻辑上标识条目何时写入
它需要将条目复制到所有追随者节点,以保持日志一致。领导者并行地向所有其他服务器发出AppendEntries rpc请求。领导者重试此操作,直到所有追随者安全地复制了新条目。当创建条目的领导者将条目复制到大多数服务器时,就会认为该条目已提交。所有之前的条目,包括以前领导者创建的条目,也被认为是提交的。一旦提交条目,领导者执行该条目并将结果返回给客户端。领导者维护它已知要提交到日志中的最大索引,并将其与AppendEntry RPC一起发送给它的追随者。一旦追随者发现该条目已经提交,它就会将条目按顺序应用到它的状态机。
Raft维护以下属性,它们共同构成日志匹配特征
- 如果不同日志中的两个条目具有相同的索引和任期,那么它们存储的是相同的指令。
- 如果不同日志中的两个条目具有相同的索引和任期,那么日志中之前的所有条目都是相同的。
当发送一个AppendEntry RPC时,领导者包含最新日志条目之前的一个条目的term和index。如果追随者在自己的日志中找不到与此条目匹配的项,则拒绝添加新条目的请求。
这种一致性检查使领导者得出这样的结论:无论何时AppendEntries RPC从追随者那里成功返回,到RPC中包含的索引为止它们都具有相同的日志。但是在领导者崩溃时,领导者和追随者的日志可能会变得不一致。
在Raft中,领导者通过强制追随者复制自己的日志来处理不一致。这意味着追随者日志中的冲突条目将被领导者日志中的条目覆盖。
领导者试图找到与追随者日志匹配的最后一个索引,删除额外的条目(如果有的话),并添加新的条目。
领导者給每个追随者维护一个nextIndex,这是领导者将发送给追随者的下一个日志条目的索引。当领导者第一次被选举成功,它会根据日志条目的最后一个索引初始化所有nextIndex值。
每当追随者返回AppendRPC失败时,领导者就会递减nextIndex并发出另一个AppendEntries RPC。最终,nextIndex将达到一个匹配到正确日志条目的值,此时AppendEntries成功,追随者可以删除无关的条目(如果有的话)并从领导者日志中添加新的条目(如果有的话)。追随者成功的AppendEntries确保了领导者的日志与之一致。
使用这种机制,当被选举成功,领导者不需要采取任何特殊的行动来恢复日志一致性。它只需要开始正常操作,日志会自动收敛,以响应Append-Entries一致性检查的失败。领导者从不重写或删除自己日志中的条目。
(java达人语: 领导者可能在任意阶段挂掉,在这不同阶段如何处理,此文有详细论述https://www.cnblogs.com/mindwind/p/5231986.html)
安全性
Raft确保某个任期的领导者已经提交了日志中所有以前任期的条目。这是为了确保所有日志是一致的,并且状态机执行相同的指令集。在领导人选举期间,RequestVote RPC包含关于候选人日志的信息。如果投票者发现它的日志比候选人更新得更及时,那么它不会投票给候选人。
Raft通过比较日志中最后一个条目的索引和任期来确定两个日志中哪个是最新的。如果日志的最后一个条目具有不同的任期,那么拥有较新任期的日志是最新的。如果日志以相同的任期结束,那么较长的日志是最新的。
集群成员:
为了确保配置更改机制的安全性,在过渡期间不能同时选举两名领导人。不幸的是,任何将服务器直接从旧配置切换到新配置的方法都是不安全的。
Raft使用两阶段方法来更改集群成员。首先,它切换到称为joint consensus的中间配置。然后,一旦提交,它就切换到新的配置。
joint consensus允许各个服务器在不同的时间在不同的配置之间进行转换,而不会损害安全性。此外, joint consensus允许集群在整个配置更改期间继续为客户端请求提供服务。
Joint consensus将新的和旧的配置结合如下:
- 日志条目被复制到在两个配置上的所有服务器。
- 新老配置中的所有服务器都可以成为领导者。
- 只有在新老配置中分别占有多数,才能达成一致(选出 领导者 和进行 Log 提交)。
当领导者收到配置更改消息时,它为join consensus C<old, new> 存储并复制条目。服务器总是使用其日志中的最新配置来做出决策,即使它没有提交。当joint consensus被提交时,只有日志中包含C<old, new>的服务器才能成为领导者。
现在,对于领导者来说,创建一个描述C<new> 的日志条目并将其复制到集群中是安全的。同样,一旦发现该配置,它将立即在每个服务器上生效。当在C<new>规则下提交新配置后,旧的配置就不相关了,不在新配置中的服务器可以被关闭。
(java达人语: 关于joint consensus文中概括地比较简略,这篇论述更加详细https://www.cnblogs.com/hzmark/p/raft_4.html)
一篇关于raft如何工作的奇妙的可视化内容在此。(http://thesecretlivesofdata.com/raft/)
更多的材料,如演讲,演示,相关论文和开源实现在此。(https://raft.github.io/)
我只研究了组成Raft的基本算法的细节,以及它提供的安全保证。这篇论文包含了更多的细节,它是很容易理解的,因为作者的首要目标是易懂。我强烈建议你去读它,即使你以前从来没有读过任何这方面资料