51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

分布式算法:深入剖析Raft算法工作机制

你好,我是猿java。

分布式算法:Paxos 是如何达成共识的? 这篇文章中,我们深入的讲解了 Paxos算法,尽管 Paxos在分布式算法的地位很重要,但是因其晦涩难懂且缺乏工程实现,因此市面上出现了不少取而代之的算法,今天我们就来分析一种和 Paxos有异曲同工之妙的的共识算法 Raft。

可以说,掌握了 Raft算法,就能比较轻松地处理绝大部分场景的容错和一致性需求,比如 Kafka等中间件底层是如何保证数据的一致性、NoSQL 是怎么做分布式存储等等,本文内容大纲如下:

img.png

  1. 什么是 Raft算法 {#1-什么是-Raft算法}

Raft 是英文"R eliable、R eplicated、R edundant、A nd F ault-T olerant"("可靠、可复制、可冗余、可容错")的首字母缩写,
它起源于 2013 年 斯坦福大学 Diego Ongaro(迭戈·安加罗) 和 John Ousterhout(约翰·奥斯特豪特) 的博士论文《In Search of an Understandable Consensus Algorithm》。
是一种用于替代 Paxos的共识算法,相比于 Paxos,Raft 的目标是更容易理解,同时安全性更高,并能提供一些额外的特性。

  1. 三种角色(身份/状态) {#2-三种角色-身份-状态}

在Raft算法 中有 Leader(领导者),Candidate(候选人),Follower(跟随者) 三种角色,也可以说成三种身份或者状态,关于它们的说明如下:

Leader(领导者)

  • 负责处理所有外部的请求,如果不是 Leader机器收到请求时,请求会被转到 Leader机器;
  • 负责向 Follower(跟随者) 同步 心跳信息;
  • 负责向 其他节点 同步日志复制 AppendEntries RPC信息;
  • 同一任期,最多只能有一个 Leader;

Candidate(候选人)

  • 主动发起选举投票;
  • 重置选举超时时间;
  • 获取 大多数 Follower(跟随者) 的投票后成为 Leader(领导者);

Follower(跟随者)

  • 响应 Leader(领导者) 的 AppendEntries RPC(空消息) 心跳请求;
  • 响应 Candidate(候选人) 的 RequestVote RPC 投票请求;
  • 响应 Leader(领导者) 的 AppendEntries RPC 日志复制请求;
  • 切换成 Candidate(候选人)角色,为自己发起选举投票;

三者的关系如下图:

img.png

  1. 如何选举 Leader {#3-如何选举-Leader}

在讲解 Leader选举之前,我们先了解 任期、随机超时、通信方式 等几个基本概念,帮助后面更好的去理解 Leader选举机制。

3.1 什么是任期 {#3-1-什么是任期}

Raft算法中的term(任期)一般包含 election(选举) 和 normal operation(工作期),每个term(任期)由单调递增的 term counter(任期编号)标识,工作期可长可短也可能不存在,比如下图(摘自官网)中 Term4 的 Split Vote(平分选票),因而未成功选举 Leader(领导者),因此工作期就不存在,需要进行下一场选举:

img.png

3.2 随机超时 {#3-2-随机超时}

在Raft算法中,随机超时是指,每个节点都随机设置一个倒计时,一旦倒计时结束,节点就被唤醒,从而切换成 Candidate(候选人) 角色,发起选举。
Raft算法就是巧妙地利用了随机超时的方法,保证在大多数情况下只有一个节点发起选举,避免多Candidate选举带来的性能问题,随机超时包含2层含义:

  1. Follower(跟随者)等待 Leader(领导者)心跳信息超时的时间间隔是随机的;
  2. Candidate(候选人)等待选举超时的时间间隔是随机的,也就是在一个随机时间间隔内,Candidate(候选人)没有赢得 major(大多数)选票,选举就无效,Candidate(候选人)需要发起新一轮的选举;

3.3 通信方式 {#3-3-通信方式}

Raft算法中节点之间采用 RPC 进行通信,这里包含三种类型的 RPC:

  • RequestVote RPCs:由 Candidate(候选人) 在选举过程中发出
  • AppendEntries RPCs:由 Leader(领导者) 发出,用来做日志复制和提供心跳机制
  • Snapshot RPCs:当 Follower日志落后 Leader太多,就会以 parallel(并行)的方式发送快照 RPC请求,帮助Follower快速同步日志

3.4 选举核心流程 {#3-4-选举核心流程}

Leader选举的核心流程图

img.png

Leader选举的核心流程为:Candidate 发起选举,任期编号+1,向其他节点发起RequestVote RPC投票请求,若获得大多数投票后成为 Leader;若收到 Leader的心跳包,则Candidate 恢复成 Follower角色。

有了上面几个基本概念的铺垫,为了更情景化的说明 Leader的选举过程,本文将以节点A、节点B、节点C 组成的集群来进行演示。

3.5 选举详解 {#3-5-选举详解}

初始状态

初始状态时,每个节点的角色都是 Follower(跟随者),Term任期编号为 0(假设任期编号从0开始),并且每个节点都伴有一个随机超时(
假设节点A:100ms,节点B:150ms,节点C:180ms),如下图:

img.png

投票请求

因为节点A 的倒计时是 100ms,3 个节点中最小的,所以,节点A 最先结束倒计时被唤醒,成功晋升为 Candidate(候选人),然后将自己的 Term counter
(任期编号) +1,同时为自己先投一票,再向其他的 Follower 发起 RequestVote RPC 请求投票,如下图:

img.png

投票响应

Follower(跟随者) 节点B 和 C 收到 Candidate(候选人)节点A 的 RequestVote Rpc 投票请求后,会做如下处理:

|-------------------|-------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | if(自己在Term任期编号1的选举中已经投过票){ 忽略请求; }else { 将选票 投给 Candidate(候选人)节点A,并且将自己的任期编号设置为1,重置自己的随机超时; } |

这里假设节点B和C在任期编号为 1 的选举中没有投过票,所以会把选票投给节点A,并且把自己的任期编号设置为 1,重置自己的随机超时,交互如下图:

img.png

投票结束

Candidate(候选人)节点A 在任期编号为 1 的选举内赢得了大多数的选票,成为本任期的 Leader(领导者),为了维持自己的 Leader(领导者)
地位,Leader(领导者)节点A 需要不间断的给 Follower(跟随者) 节点B和C 发送心跳,告诉他们自己还存活,让 节点B和C 重置
随机超时,防止节点B和C重新发起投票,整体交互如下图:

img.png

到此,一个完整的 Leader选举过程描述结束,该流程是不是和我们读书时代的选班长有异曲同工之妙?

看完上面的选举描述,不知道你会不会产生这样的疑问:假如集群中有 2个或者多个节点同时发起投票,整个过程会怎样了?

多个 Candidate问题

在上述 Leader选举的描述中我们可以发现,每个节点都有一个随机超时,因此节点被唤醒是随机的,这样大大降低了多个节点在同一时刻被唤醒成为 Candidate(候选人) 的概率,
但是小概率的事件不代表不发生,假如有 2个节点同时被唤醒,整个 Leader选举流程会怎样?

这里我们假设节点A和B的随机超时都是 100ms,这样两个节点就会同时被唤醒,成为 Candidate(候选人),首先 节点 A 和 B 会分别为自己投一票,然后向其他节点发起投票请求,如果节点A的投票请求先于节点B到达节点C,
最终,节点A 获取 2张选票,节点B 获取 1张选票,因此,节点A 获取大多数选票成为 Leader(领导者),节点B 的角色会从 Candidate 恢复成 Follower,整个交互如下图:

img.png

Split Vote 平票问题

上述描述的都是基于"奇数个节点的集群",如果集群中的节点是偶数个,结果又是怎样了,为了更好的说明问题,此处采用 4个节点的集群进行说明:

假设节点 A 和 B 的随机超时都是 100ms,这样两个节点就会同时被唤醒成为 Candidate(候选人),首先节点 A 和 B 会分别为自己投一票,然后再向其他节点请求投票,因为节点 A 和 B 已为自己投票,
根据同一任期内最多投 1票的约束,节点 A 和 B 会拒绝给对方投票, 最终 节点 A 和 B 各自只能获取 2票,这里出现了一个经典的问题:Split Vote(平分票数),该如何处理呢?

在这种"平分选票"未选出 Leader(领导者)的情况下,所有节点会全部恢复成 Follower(跟随者) 状态,重新设置随机超时时间,准备下一轮的选举。不过需要提醒的是选举的过程越长越增加了集群不可用的时长,因此要尽量避免 Split Vote问题。整个交互如下图:

img.png
img.png

脑裂问题

上文我们一直在强调:一个集群中最多只能有一个 Leader,假如在一个集群内部发生网络分区,形成了 2个小分区,会不会出现 2个Leader?如果有,该如何解决?

这里以[A,B,C,D,E] 5个节点组成的集群为例,假如原集群的Leader是节点A,如果内部出现了网络问题,节点[A,B]为一个分区,节点[C,D,E]为一个分区,节点A为原来的 Leader,节点C获得[C,D,E]分区的所有选票也成为 Leader,因此一个集群产生了 2个Leader,这就是我们常说的"脑裂问题"。

Raft是如何解决这种脑裂问题?

答案:当网络恢复正常后,两个分区的 Leader都会向其他节点发送心跳,当节点A 收到 节点C的心跳之后,发现C的任期比自己大,因此节点A恢复成Follower,因此整个集群就恢复成只有一个 Leader的状态。

整体交互如下图:

img.png

上文在对任期的描述时讲到,任期通常包含 Leader选举和 normal operation(工作期)两部分,Leader选举过程已分析完成,接下来分析 normal operation(工作期)。

  1. 如何复制日志 {#4-如何复制日志}

在讲解 log replication(如何复制日志)之前,我们需要先看看 log entry(日志条目/日志条目):

4.1 什么是日志条目 {#4-1-什么是日志条目}

日志条目 实际上是一种数据格式,主要包含索引值(Log index)、任期编号(Term)、指令(Command) 三部分,如下图(摘自官方):

img.png

  • 索引值:日志条目对应的整数索引值,它是用来标识日志条目的,是一个连续单调递增的整数;
  • 任期编号:创建这条日志条目的 Leader(领导者)的任期编号;
  • 指令:客户端请求指定的、状态机需要执行的指令;

4.2 日志复制过程 {#4-2-日志复制过程}

Leader 一旦被选举出来,就需要为客户端的请求提供服务,每一个客户端请求都包含一条将被复制状态机执行的命令,而这些指令就是通过日志复制的方式得到执行。为了更好的理解复制过程,这里给出了一张日志复制过程的简要图:

img.png

  • Leader(领导者) 接收到客户端请求后,创建一个 new entry(新日志条目),并 appends(追加)到本地日志中(Leader的日志条目为uncommitted状态);
  • Leader(领导者) 以同步的方式向所有 Follower(跟随者) 发送 AppendEntries RPC 日志条目复制请求(Follower的日志条目为uncommitted状态);
  • Leader(领导者) 得到 major(大多数) Follower(跟随者)的复制成功的响应后,Leader(领导者)将日志条目应用到它的状态机中(Leader的日志条目为committed状态);
  • Leader(领导者) 将执行的结果返回给客户端;
  • Leader(领导者) 通过心跳或新的 AppendEntries RPC 将提交了某条日志条目的状态同步给Follower(跟随者),Follower(跟随者)将日志条目状态同步到本地状态机中(Follower的日志条目为committed状态);
  • 如果 Follower(跟随者)出现崩溃、运行缓慢、网络丢包,Leader(领导者)会不断地重试 AppendEntries RPCs(即使已经对客户端作出了响应)直到所有的 Follower(跟随者)成功存储了所有的日志条目;

通过上述日志的复制过程可以看出日志的提交过程有点类似两阶段提交(2PC),不过与2PC的区别在于,Leader只需要 majority(大多数)节点的回复即可,只要过半节点处于工作状态则系统就是可用的。
然而,这种是一种比较理想的状态,假如在复制日志的过程中,出现了进程崩溃、服务器宕机等问题,就可能导致日志不一致,Raft 会如何处理呢?

4.3 日志的一致性 {#4-3-日志的一致性}

Raft算法是以领导者日志为准来实现日志的一致的,具体包含 2个步骤:

  1. Leader(领导者) 通过日志复制 RPC 的一致性检查,找到 Follower(跟随者)节点上,与自己具有相同日志条目的最大 index索引值;
  2. Leader(领导者) 强制 Follower(跟随者) 更新覆盖的不一致日志条目,实现日志的一致;

怎么理解呢?这里以一个实例来进行讲解,如下图:

img.png

图中包含了 1个 Leader 和 1个 Follower的所有日志条目,整个复制过程分以下几个步骤(步骤1-4是一致性检查机制):

  1. Leader(领导者) 当前最大日志条目索引是 10,因此 Leader(领导者) 会通过日志复制 RPC 消息将 index=9 的日志发送给 Follower(跟随者),Follower(跟随者) 判断自己没有index=9的日志,因此拒绝更新日志并响应 Leader 失败信息。
  2. Leader(领导者) 收到 Follower(跟随者) 的失败响应后,执行index-1,将 index=8的日志发送给 Follower(跟随者),Follower(跟随者) 判断自己index=8日志条目信息为term=4,x->7,和 Leader(领导则)日志条目不相同 ,因此再次拒绝更新,响应 Leader失败信息。
  3. Leader(领导者) 收到 Follower 的失败响应后,重复操作上述过程,直到 index=6;
  4. Leader(领导者) 将 index=6的日志发送给 Follower(跟随者),Follower判断自己 index=6 日志条目中的 term和command 和 Leader相同,响应日志复制成功。因此,Leader(领导者)就知道在 index=6「term=3,y->1」日志条目位置,Follower(跟随者)的日志条目与自己相同。
  5. Leader(领导者) 通过日志复制 RPC消息,强制 Follower(跟随者)复制并更新覆盖 index=6之后的所有日志条目(不一致的日志条目),达到 Follower 与 Leader的日志保持一致;
  6. 集群中多个 Follower(跟随者),只需要重复上述过程,就能最终实现了集群各节点日志的一致。

问题

为什么 Follower(跟随者) 不直接告诉 Leader(领导者) 从哪个index开始日志缺页了,而需要 Leader(领导者)一个一个去尝试,重复RPC操作,消耗网络资源?

|-----------|---------------------------------------------------------------------------------------------------------------------------------------------| | 1 | 答案:因为相同的index索引,可能来自不同的term任期,所以 Leader(领导者)需要从最大的index 往前一个个比较相同 index 的 Follower的日志条目,直到找到第一「term和command」相同的日志条目,然后将后面的日志全部覆盖更新。 |

  1. 节点变更问题 {#5-节点变更问题}

节点变更是分布式系统很常见的问题,比如,服务器扩容需要增加机器,服务器缩容需要减少机器,出现节点故障需要变更机器等等。
在Raft算法中,为了描述节点变更,作者使用 Configuration(配置) 这个重要的概念,可以把"配置"理解为集群中所有节点地址信息的集合。比如节点 A、B、C 组成的集群,那么集群的配置就是[A, B, C]集合。

集群节点的变更可能会导致集群分裂,出现 2个 Leader(领导者),如下图,集群[A,B,C] 增加节点D和E,如果发生网络分区,形成 [A,B] 和 [C,D,E] 两个小分区,
节点A 获取原配置的大多数的选票成为 Leader(领导者),节点E 获取新配置的大多数选票成为 Leader(领导者),出现了 2个 Leader(领导者),违背了Raft算法最多一个 Leader(领导者)的原则。如下图:

img.png

那么,Raft是如何在成员变更时保持集群的稳定性并且不出现 2个Leader(领导者)?

最暴力的方式就是先将旧配置集群关闭再开启新配置集群,但是这种方式有个致命的问题就是会出现一段时间内集群不可用,
而 Raft算法为了安全考虑,采用了 Joint Consensus(联合共识) 和 single-server changes(单服务器变更) 两种方式。

5.1 联合共识 {#5-1-联合共识}

joint consensus(联合共识)是指 集群从旧配置变更成新配置的过程中使用了一个过渡的中间配置,联合共识配置是新旧配置的并集,此方法允许一次性向集群中插入多个节点而不会出现脑裂等 (safety) 问题,并且整个集群在配置转换的过程中依然能够接收用户请求,从而实现配置切换对集群调用方无感知,
因为在联合共识阶段,集群会出现新旧两种配置,为了更好的工作,联合共识做了如下的约束:

  • 约束1. 新旧配置的日志会复制给新、旧配置的所有节点;
  • 约束2. 新、旧配置的任何节点都可能成为 Leader(领导者);
  • 约束3. 选举和日志复制阶段需要在新老配置上面都超多半数才能被提交生效;

下面摘取了Raft官方关于联合共识阶段配置变更的时间线描述图:

img.png

其中,虚线代表已创建但是未提交的配置项,实线代表最新的已提交的配置项。

首先,Leader(领导者) 创建 Cold,new 日志条目,并复制到新旧配置中的大多数,此时所有的日志条目都需要被联合共识。

然后,Leader(领导者) 创建 Cnew 日志条目,并复制到 Cnew(新配置)中的大多数。因此,旧配置和新配置不会存在可以同时做出决策的时间点。

鉴于此图比较晦涩难懂,因此我们以一个实例来进行讲述,假设集群有A、B、C三个节点,需要往集群中添加 D、E两个节点,看看联合共识是如何工作的。

首先, Leader(领导者) 向所有 Follower发送一条配置变更日志 Cold,new[A,B,C,D,E],告知集群要新增两个节点[D,E]。根据约束1,日志会被复制到新旧配置的所有节点。如下图:

img.png

其次,根据约束3,配置变更日志Cold,new[A,B,C,D,E] 在新旧配置中都需要大多数节点复制成功,才能被成功应用。换句话说,假设旧配置的大多数为[A,B]、新配置的大多数为[A,B,D], 那么这些节点都需要复制成功,如下图:

img.png

最后,Cold,new 被成功应用后,Leader(领导者)再发送一条新的 Cnew RPC日志复制请求,通知集群Follower(跟随者)可以使用新配置。Follower(跟随者)收到日志复制RPC后,在 Raft一致性检查机制保证下切换成新配置,Leader(领导者)因为已经处于新配置状态,所以不需要联合共识,到此,旧配置就平稳过渡到新配置,如下图:

img.png

对于新的节点D、E,Raft 会通过日志一致性检查来复制领导者的所有日志条目,从而保证它们同样能够保持日志完整性。

上文我们分析了往集群中新增2节点的流程,接下来分析上述流程为什么不会产生脑裂。我们依然假设集群产生了网络分区,形成了[A,B] 和 [C,D,E] 两个小分区:

  1. 假如 Leader(领导者)节点A 未发送 Cold,new RPC变更日志请求,[A,B] 分区依然是旧配置,节点A 是领导者;而[C,D,E]分区,当节点C 发起选举时,因为不知道节点D、E 的存在,无法获取到大多数节点的投票。因此两个分区只有一个 Leader(领导者) 节点A,符合预期。
  2. 假如 Leader(领导者)节点A 已发送 Cold,new RPC变更日志请求,此时发生了网络分区,会出现下面两种情情况:
  • 如果 Cold,new 没有被大多数节点确认,那么 Leader(领导者)节点A 无法应用该配置,[A,B] 依然是旧配置对外提供服务,[C,D,E]分区,C任然是旧配置,感知不到D,E的存在吗,所以不可能成为 Leader,D或E任何一个节点获取不到大多数选票也无法成为Leader(领导者),符合预期;
  • 如果 Cold,new 已经被大多数节点复制,那么 Leader(领导者)节点A 会应用该配,并向所有 Follower(跟随者)发送 Cnew RPC复制日志请求,因为网络分区导致 Cnew无法被联合共识,领导者 A 后续不会提交任何日志(在一些实现中会自动退位为跟随者);对于分区 [C,D,E] 无法 Cnew RPC复制日志请求,C 任然是旧配置无法获取到大多数选票,节点D,E无法获取到大多数选票,该分区也无法选举出 Leader(领导者)。符合预期。
  1. 假如 Cnew 阶段产生了分区,因为 Cold,new 已经生效,[A,B] 和 [C,D,E] 两个小分区都拿到了新配置[A,B,C,D,E],因此[A,B]分区无法获取新配置的大多数选票,无法选出新 Leader(领导者),也就不可能发生脑裂,符合预期。

尽管 joint consensus(联合共识)允许一次性向集群中插入多个节点且不会出现脑裂等问题,但由于该方法理解和实现都比较难,所以 Raft作者提出了一种改进的方法:single-server changes(单服务器变更)。

5.2 单服务器变更 {#5-2-单服务器变更}

单服务器变更,就是每次只能有一个节点服务器成员变更。如果需要变更多个服务器节点,则需要执行多次单服务器变更。 我们还是以图文的方式来进行解释:

假如 集群有节点A、节点B、节点C,现在需要增加 2个节点(节点D,节点E),增加的方式是先增加节点D

img.png

  • 第一步,Leader(领导者)节点A 向新节点D 同步数据;
  • 第二步,Leader(领导者)节点A 将新配置[A, B, C, D]作为一个日志条目,复制到新配置中所有节点(节点 A、B、C、D)上,然后将新配置的日志条目应用(Apply)到本地状态机,完成单节点变更。

同理再增加节点E:

img.png

  • 第一步,Leader(领导者)节点A 向新节点E 同步数据;
  • 第二步,Leader(领导者)节点A 将新配置[A, B, C, D, E]作为一个日志条目,复制到新配置中所有节点(节点 A、B、C、D、E)上,然后将新配置的日志条目应用(Apply)到本地状态机,完成单节点变更。

删除节点E:

img.png

  • 第一步,先删除 节点 E;
  • 第二步,Leader(领导者)节点A 将新配置[A, B, C, D]作为一个日志条目,复制到新配置中所有节点(节点 A、B、C、D)上,然后将新配置的日志条目应用(Apply)到本地状态机,完成单节点变更。

通过上述对单服务器的增加和删除可以看出,每次单服务器节点的增减,可以保证新旧集群至少存在一个交集服务器节点,这样就不会在新旧配置同时存在 2个"大多数",从而保证集群只能有一个 Leader(领导者)。

特别注意

在作者Diego Ongaro(迭戈·安加罗) bug in single-server membership changes 的文章中特别说明了,单服务器变更的方式在串行化的方式下可以保证一个集群
只能有一个 Leader,但是在并发的、竞争可能导致多个 Leader,从而导致安全违规(脑裂)。

  1. Safety {#6-Safety}

前面章节描述了 Raft 如何做 Leader Election(Leader选举) 和 Log Replication(日志复制)。然而,到目前为止所讨论的机制并不能充分地保证每一个状态机会按相同的顺序执行相同的指令。比如说,一个 Follower(跟随者) 可能会进入不可用状态,在此期间,Leader 可能提交了若干的日志条目,然后这个 Follower 可能被选举为新Leader 并且用新的日志条目去覆盖这些日志条目。这样就会造成不同的状态机执行不同的指令的情况。
对于上述问题,Raft 如何保证安全?

6.1 选举约束 {#6-1-选举约束}

  1. 同一任期内每个节点最多只能投票 1次,并且按照 first-come-first-served(先来先服务) 的原则
  2. 日志条目的传送只能从 Leader 到 Follower,Leader 从来不会覆盖本地日志中已有的日志
  3. Candidate(候选人) 只有获得集群中大多数选票才能成为 Leader(领导者)
  4. 日志完整性高的 Follower(跟随者)拒绝投票给日志完整性低的 Candidate(候选人),这里的日志指的是已复制未commit状态。也就是说,即便 Candidate(候选人)的 term 大于 Follower(跟随者)的 term,假如 Candidate(候选人) 向 Follower(跟随者)发送了一条投票RPC,如果当前消息中的term 小于 Follower(跟随者)最后一条消息的 term,则 Follower(跟随者) 拒绝给 Candidate(候选人)投票

6.2 Leader只能提交任期内的日志条目 {#6-2-Leader只能提交任期内的日志条目}

首先我们以图文的方式来展示一个已经被存储到大多数节点的日志条目,仍然有可能会被新 Leader覆盖的场景:

img.png

  • 在图A中,S1是 Leader,将index=2的日志复制给了S2,此时S1的数据还没有复制大多数节点
  • 在图B中,S1宕机了,S5 从 [S2,S3,S4,S5] 获得大多数选票成为 Leader,任期编号为3,然后收到客户端的指令,将日志存放在 index=2 位置上
  • 在图C中,S5宕机了,S1重启,假如S1当选为 Leader,然后S1继续将它在任期2的日志条目复制给[S2,S3,S4]成功,但是还未被提交
  • 情况1:在图D中,假设S1在提交日志之前宕机,S5重启,因为S5最后日志条目上的任期为3,大于[S2,S3,S4]的任期编号2,所以S5可以得到[S2,S3,S4]大多数选票成为 Leader,然后 S5继续将它在任期3的日志条目复制到大多数节点[S2,s3,S4],因此覆盖了S1复制给[S2,S3]中 index=2处的日志
  • 情况2:在图E中,S1在宕机之前把任期3的日志复制到大多数节点的index=3处,那么 S5就不可能成为 Leader,这种情况下,之前所有的日志被提交了

为了解决上图中日志被覆盖的问题,Raft 规定 Leader只能提交任期内的日志条目。

  1. 实际使用 {#7-实际使用}

框架 Etcd、Consul、CockroachDB 使用了Raft算法

Redis 哨兵 Leader选举 使用了 Raft

百度开源的RPC框架 Braft 是基于 Raft协议

  1. 总结 {#8-总结}

  • 本文主要从 Leader选举,日志复制,集群成员变更 讲述了 Raft的工作机制;
  • Raft算法要求每个任期最多只能有一个Leader;
  • Raft算法是以Leader的日志为准来进行日志复制,而且日志必须是连续的;
  • Raft算法可以通过 Joint Consensus(联合共识) 和 single-server changes(单服务器变更) 两种方式,保证集群成员变更时最多只有一个 Leader;
  • Raft算法能实现强一致性,也就是线性一致性(Linearizability),但需要客户端协议的配合;
  1. 参考文档 {#9-参考文档}

raft.pdf

The Raft Consensus Algorithm

动画演示raft算法

single-server membership changes

bug in single-server membership changes

  1. 鸣谢 {#10-鸣谢}

赞(2)
未经允许不得转载:工具盒子 » 分布式算法:深入剖析Raft算法工作机制