raft 图解 (秒懂)

raft算法之因此容易理解,其一是他将一致性问题划分红几个子问题,这几个子问题都是独立、可理解和解释的。从传统的思惟来说,对于一个复杂的系统或者工程,都是大化小,分解实现,而后去尝试融合解决总体逻辑。mysql

一、Raft 详解

Raft 算法是分布式系统开发首选的共识算法。好比如今流行 Etcd、Consul、Nacos。算法

若是掌握了这个算法,就能够较容易地处理绝大部分场景的容错一致性需求。好比分布式配置系统、分布式 NoSQL 存储等等,轻松突破系统的单机限制。sql

Raft 算法是经过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。数据库

1.1 Raft 角色

跟随者(Follower)普通群众,默默接收和来自领导者的消息,当领导者心跳信息超时的时候,就主动站出来,推荐本身当候选人。数组

候选人(Candidate)候选人将向其余节点请求投票 RPC 消息,通知其余节点来投票,若是赢得了大多数投票选票,就晋升当领导者。安全

领导者(Leader)霸道总裁,一切以我为准。处理写请求、管理日志复制和不断地发送心跳信息,通知其余节点“我是领导者,我还活着,大家不要”发起新的选举,不用找新领导来替代我。服务器

以下图所示,分别用三种图表明跟随者、候选人和领导者。网络

角色

1.1 数据库服务器app

如今咱们想象一下,有一个单节点系统,这个节点做为数据库服务器,且存储了一个值为 X。分布式

数据库服务器

左边绿色的实心圈就是客户端,右边的蓝色实心圈就是节点 a(Node a)。Term 表明任期,后面会讲到。

客户端

客户端向单节点服务器发送了一条更新操做,设置数据库中存的值为 8。单机环境下(单个服务器节点),客户端从服务器拿到的值也是 8。一致性很是容易保证。

客户端向服务器发送数据

但若是有多个服务器节点,怎么保证一致性呢?好比有三个节点:a,b,c。以下图所示。这三个节点组成一个数据库集群。客户端对这三个节点进行更新操做,如何保证三个节点中存的值一致?这个就是分布式一致性问题。Raft 算法就是来解决这个问题的。固然还有其余协议也能够保证,本篇只针对 Raft 算法。

在多节点集群中,在节点故障、分区错误等异常状况下,Raft 算法如何保证在同一个时间,集群中只有一个领导者呢?下面就开始讲解 Raft 算法选举领导者的过程。

1.2 初始状态

初始状态下,集群中全部节点都是跟随者的状态。

以下图所示,有三个节点(Node) a、b、c,任期(Term)都为 0。

mark

Raft 算法实现了随机超时时间的特性,每一个节点等待领导者节点心跳信息的超时时间间隔是随机的。好比 A 节点等待超时的时间间隔 150 ms,B 节点 200 ms,C 节点 300 ms。那么 a 先超时,最早由于没有等到领导者的心跳信息,发生超时。以下图所示,三个节点的超时计时器开始运行。

超时时间

1.3 发起投票

当 A 节点的超时时间到了后,A 节点成为候选者,并增长本身的任期编号,Term 值从 0 更新为 1,并给本身投了一票。

  • Node A:Term = 1, Vote Count = 1。
  • Node B:Term = 0。
  • Node C:Term = 0。

成为候选者

1.4 成为领导者的简化过程

咱们来看下候选者如何成为领导者的。

Leader 选举

  • 第一步:节点 A 成为候选者后,向其余节点发送请求投票 RPC 信息,请它们选举本身为领导者。
  • 第二步:节点 B 和 节点 C 接收到节点 A 发送的请求投票信息后,在编号为 1 的这届任期内,尚未进行过投票,就把选票投给节点 A,并增长本身的任期编号。
  • 第三步:节点 A 收到 3 次投票,获得了大多数节点的投票,从候选者成为本届任期内的新的领导者。
  • 第四步:节点 A 做为领导者,固定的时间间隔给 节点 B 和节点 C 发送心跳信息,告诉节点 B 和 C,我是领导者,组织其余跟随者发起新的选举。
  • 第五步:节点 B 和节点 C 发送响应信息给节点 A,告诉节点 A 我是正常的。

1.5 领导者的任期

英文单词是 term,领导者是有任期的。

  • 自动增长:跟随者在等待领导者心跳信息超时后,推荐本身为候选人,会增长本身的任期号,如上图所示,节点 A 任期为 0,推举本身为候选人时,任期编号增长为 1。

  • 更新为较大值:当节点发现本身的任期编号比其余节点小时,会更新到较大的编号值。好比节点 A 的任期为 1,请求投票,投票消息中包含了节点 A 的任期编号,且编号为 1,节点 B 收到消息后,会将本身的任期编号更新为 1。

  • 恢复为跟随者:若是一个候选人或者领导者,发现本身的任期编号比其余节点小,那么它会当即恢复成跟随者状态。这种场景出如今分区错误恢复后,任期为 3 的领导者受到任期编号为 4 的心跳消息,那么前者将当即恢复成跟随者状态。

  • 拒绝消息:若是一个节点接收到较小的任期编号值的请求,那么它会直接拒绝这个请求,好比任期编号为 6 的节点 A,收到任期编号为 5 的节点 B 的请求投票 RPC 消息,那么节点 A 会拒绝这个消息。

  • 一个任期内,领导者一直都会领导者,直到自身出现问题(如宕机),或者网络问题(延迟),其余节点发起一轮新的选举。

  • 在一次选举中,每个服务器节点最多会对一个任期编号投出一张选票,投完了就没了。

假设一个集群由 N 个节点组成,那么大多数就是至少 N/2+1。例如: 3 个节点的集群,大多数就是 2。

1.6 防止多个节点同时发起投票

为了防止多个节点同时发起投票,会给每一个节点分配一个随机的选举超时时间。这个时间内,节点不能成为候选者,只能等到超时。好比上述例子,节点 A 先超时,先成为了候选者。这种巧妙的设计,在大多数状况下只有一个服务器节点先发起选举,而不是同时发起选举,减小了因选票瓜分致使选举失败的状况。

成为候选者

1.7 触发新的一轮选举

若是领导者节点出现故障,则会触发新的一轮选举。以下图所示,领导者节点 B 发生故障,节点 A 和 节点 B 就会从新选举 Leader。

领导者故障

  • 第一步 :节点 A 发生故障,节点 B 和节点 C 没有收到领导者节点 A 的心跳信息,等待超时。
  • 第二步:节点 C 先发生超时,节点 C 成为候选人。
  • 第三步:节点 C 向节点 A 和 节点 B 发起请求投票信息。
  • 第四步:节点 C 响应投票,将票投给了 C,而节点 A 由于发生故障了,没法响应 C 的投票请求。
  • 第五步:节点 C 收到两票(大多数票数),成为领导者。
  • 第六步:节点 C 向节点 A 和 B 发送心跳信息,节点 B 响应心跳信息,节点 A 不响应心跳信息。

1.8 Raft 算法的几个关键机制

Raft 算法经过如下几个关键机制,保证了一个任期只有一位领导,极大减小了选举失败的状况。

  • 任期
  • 领导者心跳信息
  • 随机选举超时时间
  • 先来先服务的投票原则
  • 大多数选票原则

2. Raft算法的典型应用

Raft算法的典型应用包括:

  • 领导选举

  • 日志复制

对于一个集群只有一个leader(领导),那么咱们就很容易理解。只要领导操做同步到对应的followers(跟随者),数据必然一致。当leader宕机,须要进行领导选举。

日志复制其实就是同步操做数据的过程。leader将操做日志同步到其余节点。

安全性:如何安全的同步,在不一样的状况,咱们都能保证一致性,这也就是安全性须要考虑的问题。

其实就是如此,raft首先假设了领导选举。而后实现了日志复制,最后在安全问题上解决上面的漏洞问题。

1.领导选举

目的:当集群初始化或者领导gg的时候选出一个新的领导。毕竟一个集群不能没有领导,若是没有,那么这个集群就不可用了。

触发机制:经过心跳。
1.png
这幅图展示了跟随者、候选者和领导者之间的状态转换关系,下面主要介绍他们的转换流程。

跟随者

若是他能持续从领导者或者候选者接收到有效的RPCs,那么他的状态就不会变。一直保持跟随者身份。但若是没有持续接受,也就是在一段时间没收到有效的RPCs,那就选举超时,他会变成候选者。

候选者

要变为候选者,也就意味着他要开启一轮新的选举。那么跟随者会增长本身的任期号,转为候选者。

成为候选者后,本身会并行发送一个为本身投票的RPCs请求 给其余服务器。

成为候选者的整个过程也会保持当前状态,知道知足下面三个条件

  • 他赢得选举,转变为领导制
  • 其余节点赢了,他转为跟随者
  • 一段时间没有任何人获胜。说明选票被瓜分,重复执行。

这里须要注意的点:

1.对于同一任期号,每一个节点一会投一张票。好比服务器A做为候选者生成了编号为5的任期号,那么若是接收到其余节点的编号为5的任期号的投票请求,他会忽略。这个过程遵循的是先来先投的原则。
2.候选者接收到领导者的声明。会判声明中RPC的任期号,若是比本身的还小,那么他还会保持候选者身份,不然转为跟随者。
3.对于上面第三点,重复执行,Raft采用随机超时选举时间进行了优化。下降选票瓜分的可能性。

2.Raft日志复制

直接去理解日志复制,是很容易的,客户端的一条指令到达,领导者会为这条指令建立一条日志条目,而且并行发送到其余跟随者。当日志被安全复制(所谓安全复制后面会有),领导会将日志应用到状态机(好比若是是mysql的insert,那么就是执行insert操做),而后响应客户端。

2.png

如上图,每条日志都会有对应的任期号,和指令。
每一个日志都会有对应的索引。

raft日志匹配特性
1.若是在不一样的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
2.若是在不一样的日志中的两个条目拥有相同的索引和任期号,那么他们以前的全部日志条目也所有相同。

第一点:一个任期只有一个领导人,而且领导人在一个任期中对于同一索引日志,只会建立一条日志,是不会改变的,是肯定的。这就保证第一点成立。
第二点:要想所有相同,就要保证跟随者获得的日志是领导者发送的顺序附加上去的。领导者在发送新的日志时,会附加这条日志以前日志的索引和任期号。若是跟随者发现数据匹配,才会附加上去,不然拒绝。就是一个个状态保证了日志的匹配特性。

对于日志不一致的现象,raft是经过跟随者强制复制领导者的日志来保证的。

3.png

如上图,对于a-f,最终都会和leader同步,也就是说,d会丢弃日志。f的对应日志也会被丢弃和覆盖。

其实就是经过日志覆盖解决。可是对于日志覆盖,咱们就会想到一个问题,会不会覆盖已经提交的日志(日志对应指令已经返回给客户端)。那固然不会,若是真有这样,就会有不一致,或者指令丢失现象。

那么如何去作覆盖跟随者日志

其实就是跟随者在append日志的时候,会进行错误校验。

在候选者成为领导者的时候,会为每一个跟随者初始化一个nextIndex数组,数组的值初始化为本身最后条日志+1,其实就是理想化状态,默认认为日志都已经同步成功。可是理想总会由于各类缘由致使不正确,就用上面那张图。leader会初始化全部nextIndex为11,可是在同步日志的过程当中。f节点会出现校验错误的响应。由于f节点10索引对应的日志和leader10索引对应的日志不相同(主要是根据任期号判断)

这里再强调一下为何根据任期号就能够判断日志是否一致,就是上面所说的日志匹配原则。

3.Raft算法的安全性

咱们明白了如何选举和日志复制,可是没有考虑安全性问题。其实我上慢提到,好比一个宕机好久的跟随着会被选为领导者,进行日志覆盖操做会有丢失问题。

其实解决这个办法很简单,就是在领导选举的时候,只能让安全的节点当leader,所谓安全,就是对应节点拥有当前领导者已经提交的全部日志。Raft就是这么作的。

Raft中节点在投票的时候,会判断被投票的候选者对应的日志是否至少和本身同样新。若是不是,则不会给该候选者投票。

日志比较的方法:
1.最后一条日志的任期号。若是大说明新。若是小,说明不新。若是相等。跳到2
2.判断索引长度。大的更新。

还有一个问题,就是领导人不能保证一个已经在大多数节点存在的日志是否已经提交。

4.png

a、b、c、d、e表明不一样的任期阶段

(a)S1是leader。同步任期2的数据给S2

(b)S1宕机,S5当选(S三、S四、S5投票),产生任期3的日志

(c)S5宕机,S1恢复当选(同步任期2的数据给S3)。

(d)S1宕机,S5当选(由于他的任期日志比其余的都新),复制了任期3的全部数据。

假如说在c阶段,S1提交了任期2的数据,那么若是出现d,则会致使任期2数据被覆盖,丢失。也就是说,S1在任期4时候,不能保证已经在大多数节点存在的日志(任期2的日志)是否提交。

因此raft永远不会经过计算副本数目的方式(大多数存在)去提交一个以前任期内(任期2)的日志条目。只有领导人当前任期里的日志条目经过计算副本数目能够被提交(e阶段)。这样以前任期的数据也会被提交。

那这里我理解一点就是,加入S1当选为leader,如图c状态,那么,若是再也不有新的日志出现,任期2对应的日志就不会提交。那么会致使客户端对应的任期2请求失败。

跟随者和候选人宕机

这个就比较容易理解了,宕机的话RPCs就会失败,Raft经过无限重试却解决这个问题。
因此对于每一个RPCs,作到幂等和无限重试,在节点恢复后,就仍是会保证一致性状态。

Raft集群成员变化

对于集群成员配置变化,若是直接更新每台机器配置,那么就会有安全性问题。觉得对于同一时刻,不一样节点使用的不一样的配置去执行算法逻辑,这就是不安全的。

5.png

如图,蓝色表明新的配置。绿色表明老的配置。Old状态有三台机器Server一、二、3。 New加入两台server四、5。

那么随着配置时间应用的不一样,可能会致使选举出两个leader。
好比server一、2使用老配置,那么1和2都有可能被当选为leader。三、四、5使用心得配置,他们之中的一个也会被当选为leader。

其实这个问题缘由就是节点使用了不一样配置执行算法逻辑。为了解决这个问题,raft采用两阶段方法(其实只须要保证不会让新或者旧配置单独做出决定就行)

6.png

raft吧配置看成普通日志形式去提交。

为了实现两阶段,引入了C(old、new)配置。
还有一点就是,一点一个新的配置日志增长到对应的节点日志中,那么该节点就会马上使用这条新的日志配置。

对于C(old、new)配置,其实就是只有同时知足old和new配置的时候才会生效。

这样理想状态下,若是拥有C(old、new)配置的节点当选为leader。而且提交了该配置,那么说明C(old、new)配置已经在大多数节点应用。下次选举的产生的leader日志中必然会有该配置。这个时候在建立一条新的C(new)配置提交,便可。