简历上写精通 Raft 算法,为什么经常被淘汰?

2024年 5月 13日 78.8k 0

前两天,面试了一个在大厂工作了 8年的 Java技术专家,简历上写着“精通分布式算法,包括 Raft,Paxos”,于是,先简单地问了下:能聊聊 Raft算法中有哪几种角色?结果,支支吾吾硬是没有回答出来。

所以,在简历上慎用精通二字,除非真的是这个领域的专家,借此机会,一起来深入研究下 Raft算法。

简历上写精通 Raft 算法,为什么经常被淘汰?-1

一、Raft是什么?

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

二、三种角色

在 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(候选人)角色,为自己发起选举投票;

三者的关系如下图:

简历上写精通 Raft 算法,为什么经常被淘汰?-2

三、如何选举 Leader?

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

1. 任期

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

简历上写精通 Raft 算法,为什么经常被淘汰?-3

2. 随机超时

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

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

3. 通信方式

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

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

4. 选举核心流程 

Leader 选举的核心流程图:

简历上写精通 Raft 算法,为什么经常被淘汰?-4

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

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

5. 选举详解初始状态

(1) 初始状态

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

简历上写精通 Raft 算法,为什么经常被淘汰?-5

(2) 投票请求

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

简历上写精通 Raft 算法,为什么经常被淘汰?-6

(3) 投票响应

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

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-7

(4) 投票结束

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

简历上写精通 Raft 算法,为什么经常被淘汰?-8

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

假如 Leader选举过程中出现异常,比如:集群中有 2个或者多个节点同时结束倒计时发起投票,整个过程会怎样?

(5) 多个 Candidate 问题

在上述 Leader选举的描述中我们可以发现,每个节点都有一个随机超时机制,因此节点被唤醒是随机的,这样大大降低了多个节点在同一时刻被唤醒成为 Candidate(候选人) 的概率,但是小概率的事件不代表不发生,这里我们以节点 A 和 B同时发起投票为例:

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

简历上写精通 Raft 算法,为什么经常被淘汰?-9

(6) Split Vote 平票问题

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

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-10

简历上写精通 Raft 算法,为什么经常被淘汰?-11

(7) 脑裂问题

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

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

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

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

整体交互如下图:

简历上写精通 Raft 算法,为什么经常被淘汰?-12

上文在描述 Leader的选举过程中提到 3种 RPCs,那么它们是什么呢?接下来我们就来分析 Raft的另外一个重要知识点:日志复制。

四、如何复制日志?

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

1. 日志条目

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

简历上写精通 Raft 算法,为什么经常被淘汰?-13

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

2. 日志复制过程

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

简历上写精通 Raft 算法,为什么经常被淘汰?-14

  • 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 会如何处理呢?

3. 日志一致性

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

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-15

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

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

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

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

五、节点变更问题   

节点变更是分布式系统很常见的问题,比如,服务器扩容需要增加机器,服务器缩容需要减少机器,出现节点故障需要变更机器等等。在 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(领导者)的原则。如下图:

简历上写精通 Raft 算法,为什么经常被淘汰?-16

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

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

1. 联合共识

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

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-17

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

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

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

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-18

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

简历上写精通 Raft 算法,为什么经常被淘汰?-19

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

简历上写精通 Raft 算法,为什么经常被淘汰?-20

对于新的节点 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(领导者)。符合预期。

假如 Cnew 阶段产生了分区,因为 Cold,new 已经生效,[A,B] 和 [C,D,E] 两个小分区都拿到了新配置[A,B,C,D,E],因此[A,B]分区无法获取新配置的大多数选票,无法选出新 Leader(领导者),也就不可能发生脑裂,符合预期。

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

2. 单服务器变更

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

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

简历上写精通 Raft 算法,为什么经常被淘汰?-21

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

同理再增加节点 E:

简历上写精通 Raft 算法,为什么经常被淘汰?-22

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

删除节点 E:

简历上写精通 Raft 算法,为什么经常被淘汰?-23

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

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

特别注意:

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

六、Safety  

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

1. 选举约束

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

2. 只能提交任期内的日志

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

简历上写精通 Raft 算法,为什么经常被淘汰?-24

在图 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 只能提交任期内的日志条目。

七、实际使用 

Raft是现在很多分布式框架常用的一种算法,掌握这个算法,可以得心应手地处理绝大部分场景的容错和一致性需求,比如分布式配置系统、分布式 NoSQL 存储等等,轻松突破系统的单机限制。下面列举了几款使用 Raft算法的经典框架:

  • Etcd、Consul、CockroachDB 使用了 Raft 算法
  • Redis 哨兵 Leader 选举 使用了 Raft
  • 百度开源的 RPC 框架 Braft 是基于 Raft 协议

八、总结  

本文主要从 Leader 选举,日志复制,集群成员变更 讲述了 Raft 的工作机制;

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论