Raft 实现从0-1 基础篇

Raft 基础

Posted by WR on November 12, 2022

基础概念

介绍

​ Raft 是一种为了管理复制日志的一致性算法。它提供了和 Paxos 算法相同的功能和性能,但是它的算法结构和 Paxos 不同,使得 Raft 算法更加容易理解并且更容易构建实际的系统。为了提升可理解性,Raft 将一致性算法分解成了几个关键模块,例如领导人选举、日志复制和安全性。同时它通过实施一个更强的一致性来减少需要考虑的状态的数量。一项用户研究的结果表明,对于学生而言,Raft 算法比 Paxos 算法更加容易学习。Raft 算法还包括一个新的机制来允许集群成员的动态改变,它利用重叠的大多数来保证安全性。

复制状态机

​ 一致性算法是从复制状态机的背景下提出的。在这种方法中,一组服务器上的状态机产生相同状态的副本,并且在一些机器宕掉的情况下也可以继续运行。复制状态机在分布式系统中被用于解决很多容错的问题。例如,大规模的系统中通常都有一个集群领导人,像 GFS、HDFS 和 RAMCloud,典型应用就是一个独立的复制状态机去管理领导选举和存储配置信息并且在领导人宕机的情况下也要存活下来。比如 Chubby 和 ZooKeeper。

图 1

图 1 :复制状态机的结构。一致性算法管理着来自客户端指令的复制日志。状态机从日志中处理相同顺序的相同指令,所以产生的结果也是相同的。

​ ==复制状态机通常都是基于复制日志实现的==,如图 1。每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。每一个日志都按照相同的顺序包含相同的指令,所以每一个服务器都执行相同的指令序列。因为每个状态机都是确定的,每一次执行操作都产生相同的状态和同样的序列。

​ 一致性算法的任务是保证复制日志的一致性。服务器上的一致性模块接收客户端发送的指令然后添加到自己的日志中。它和其他服务器上的一致性模块进行通信来保证每一个服务器上的日志最终都以相同的顺序包含相同的请求,即使有些服务器发生故障。一旦指令被正确的复制,每一个服务器的状态机按照日志顺序处理他们,然后输出结果被返回给客户端。因此,服务器集群看起来形成了一个高可靠的状态机。

实际系统中使用的一致性算法通常含有以下特性:

  • 安全性保证(绝对不会返回一个错误的结果):在非拜占庭错误情况下,包括网络延迟、分区、丢包、重复和乱序等错误都可以保证正确。
  • 可用性:集群中只要有大多数的机器可运行并且能够相互通信、和客户端通信,就可以保证可用。因此,一个典型的包含 5 个节点的集群可以容忍两个节点的失败。服务器被停止就认为是失败。它们稍后可能会从可靠存储的状态中恢复并重新加入集群。
  • 不依赖时序来保证一致性:物理时钟错误或者极端的消息延迟只有在最坏情况下才会导致可用性问题。
  • 通常情况下,一条指令可以尽可能快的在集群中大多数节点响应一轮远程过程调用时完成。小部分比较慢的节点不会影响系统整体的性能。
Raft 一致性算法

​ Raft 通过选举一个杰出的领导人,然后给予他全部的管理复制日志的责任来实现一致性。领导人从客户端接收日志条目(log entries),把日志条目复制到其他服务器上,并告诉其他的服务器什么时候可以安全地将日志条目应用到他们的状态机中。拥有一个领导人大大简化了对复制日志的管理。例如,领导人可以决定新的日志条目需要放在日志中的什么位置而不需要和其他服务器商议,并且数据都从领导人流向其他服务器。一个领导人可能会发生故障,或者和其他服务器失去连接,在这种情况下一个新的领导人会被选举出来。

通过领导人的方式,Raft 将一致性问题分解成了三个相对独立的子问题,这些问题会在接下来的子章节中进行讨论:

  • 领导选举:当现存的领导人发生故障的时候, 一个新的领导人需要被选举出来
  • 日志复制:领导人必须从客户端接收日志条目(log entries)然后复制到集群中的其他节点,并强制要求其他节点的日志和自己保持一致。
  • 安全性:如果有任何的服务器节点已经应用了一个确定的日志条目到它的状态机中,那么其他服务器节点不能在同一个日志索引位置应用一个不同的指令。
Raft 结点角色

​ Leader 节点负责处理所有客户端的请求, 当接收到客户端的写入请求时, Leader节点 会在本地追加一条相应的日志,然后将其封装成消息发送到集群中其他的 Follower节 点。当 Follower节点收到该消息时会对其进行响应。如果集群中多数(超过半数〉节 点都己收到该请求对应的日志记录时, 则 Leader 节点认为该条日志记录己提交 Ccommitted),可以向客户端返回响应。 Leader 还会处理客户端的只读请求, 其中涉 及一 个简 单 的优化, 后面介绍具体实现 时,再进行 详细介绍。 Leader 节点的另一项工 作是定期向集群中的 Follower 节点发送心跳消息,这主要是为了防止集群中的其他 Follower 节点的选 举计 时器超时而触发新 一 轮选举。

​ Follower 节点不会发送任何请求,它们只是简单地响应来自 Leader或者 Candidate 的 请求: Follower节点也不处理 Client的请求,而是将请求重定向给集群的 Leader节点 进行处理 。

​ Candidate节点 是由 Follower节点转换而来的,当 Follower节点长时间没有收到 Leader 节点发送的心跳消息时,则该节点的选举计时器就会过期,同时会将自身状态转换成 Candidate,发起新 一 轮选举。选举的具体过程在下面详细描述 。

超时时间

​ 心跳超时时间(heartbeat timeout),也就 是 Leader节点向集群中其他 Follower节点发送心跳消息的时间间隔。

​ 选举超时时间(election timeout),在 Raft协议中有两个时间控制 Leader选举发生,其中一个是选举超时时间(electiontimeout),每个 Follower 节点 在接收不到 Leader 节点的 心跳消 息之后,并不会立即发起新一轮选举,而是 需要等待一段时间之后才切换成 Candidate状态发起新一轮选举。这段等待时长就是这里所说的election timeout 。之所以这样设计,主要是 Leader 节点发送的心跳消息可能因为瞬间的网络延迟或程序 瞬间的卡顿而迟到(或是丢失),因此就触发新一轮选举是没有必要的 。 election timeout 一 般设 置为 150ms~300ms 之间的随机数。

领导选举

结点说明,如下图

image

​ 图2

正常选举

当集群初始化时,所有节点都处于 Follower 的状态,此时的集群中没有 Leader 节点。当 Follower 节点一段时间 (选举计时器超时)内收不到 Leader 节点的心跳消息,则认为 Leader 节点出现故障导致其任期((Term)过期, Follower 节 点会转换成 Candidate 状态,发起新 一 轮的 选举。所谓 “任期( Term〕”,实际上就是一个全局的、连续递增的整数,在 Raft 协议中每进 行一次选举,任期(Term)加一,在每个节点中都会记录当前的任期值(currentTerm)。每一个 任期都是从一次选举开始的,在选举时,会出现一个或者多个 Candidate 节点尝试成为 Leader 节点,如果其中一个 Candidate 节 点赢得选举,则该节点就会切换为 Leader 状态并成为该任期 的 Leader 节点,直到该任期结束 。

​ 此时节点 A 由于长时间未收到 Leader 的心跳消息,就会切换成为 Candidate状态并发起选举(节点 A的选举计时器 election timer己被重置)。在选举过程中, 节点 A 首先会将自己的选票投给自己,并会向集群中其他节点发送选举请求( RequestVote)以 获取其选票,如下图 3 所示 ,此时的 节点 B 和节 点 C 还都是处于 Term=0 的任期 之中,且 都是 Follower 状态,均未投出 Term = 1 任期中的选票,所以节点 B 和节点 C 在接收到节点 A 的 选举请求后会将选票投给节点 A,另外,节点 B、 C 在收到节点 A 的选举请求的同时会将选举定时器重置,这是为了防止一个任期中同时出现多个 Candidate 节点,导致选举失败,如下图 4 所示。注意,节点 B 和节点 C 也会递增自身记录的 Term 值。

image

​ 图3

image

​ 图4

​ 在节点 A 收到节点 B、 C 的投票之后,其收到了集群中超过半数的选票,所以在 Term = 1 这个任期中,该集群的 Leader 节点就是节点 A,其他节点将切换成 Follower 状态,如图 5 所 示。集群中的节点除 了记录当期任期号(currentTerrn),还会记录在该任期中当前节点的投票结果(VoteFor)。

image

​ 图5

​ 继续前面的示例,成为 Term = 1 任期的 Leader 节 点之后,节点 A 会定期向集群中的其他节点发送心跳消息,如图 (6) 所示,这样就可以防止节点 B 和节点 C 中的选举计时器( election timer)超时而触发新一轮的选举;当节点 B 和节点C (Follower) 收到节点 A 的心跳消息之后会重置选举计时器,如 图 (7) 所示 , 远小于选举超时时间( election timeout)。由此可见 ,心跳超时时间( heartbeat timeout)需要远小于选举超时时间(election timeout)。

image

​ 如果有两个或两个以上节点的选举计时器同时过期,则这些节点会 同时由 Follower 状态切换成 Candidate 状态,然后同时触发新一轮选举,在该轮选举中,每个 Candidate节点获取的选票都不到半数,无法选举出 Leader节点,那么 Raft 协议会如何处理呢? 这种情况确实存在,假设集群中有 4 个节点,其中节点 A 和节点 B 的选举计时器同时到期,切换到 Candidate 状态并向集群中其他节点发出选举请求,如图 8 所示。这里假设节点 A 发出的选举请求先抵达节点 C,节点 B 发出的选举请求先抵达节点 D,如 图 9 所示 ,节点 A 和节点 B 除了得到自身的选 票之 外,还分别得到了节点 C 和节点 D 投出的选票,得票数都是 2,都没有超过半数。在这种情况下, Term = 2 这个任期会以选举失败结束,随着时间的流逝,当任意节点的选举计时器到期之后,会再次发起新一轮的选举。==election timeout 是在一个时间区间内取的随机数==,所以在配置合理的时候,像上述情况 多次出现的概率并不大。

image

​ 继续上面的示例,这里假设节点 A 的选举计时器再次到期(此次节点 B、 C、 D 的选举计 时器并未到期) - 每个节点的选举超时时间是个随机范围,它会切换成 Candidate 状态并发起新一轮选举(Term=3),如图 10 所示, 其中节点 B 虽然处于 Candidate 状态,但是接收到 Term 值比自身记录的 Term 值大的请求时, 节点会切换成 Follower 状态井更新自身记录的 Term 值,所以该示例中的节点 B 也会将选票投 给节点 A,如图 11 所示。

image

​ 在获取集群中半数以上的选票并成为新任期( Term=3)的 Leader 之后,节点 A 会定期向 集群中其他节点发送心跳消息;当集群中其他节点收到 Leader节点的心跳消息的时候,会重置 选举定时器。

异常选举
结点宕机

​ 在系统运行一段时间后,集群当前的 Leader 节点( A )因为故障 而看机,此时将不再有心跳消息发送到集群的其他 Follower 节点(节点 B、 C、 D ),一段时间 后,会有一个 Follower 节点的选举计时器最先超时,这里假设节点 D 的选举计时器最先超时, 然后它将切换为 Candidate状态井发起新一轮选举。

​ 当节点 B 和节点 C 收到节点 D 的选举请求后,会将其选票投给节点 D,由于节点 A 己经宕机,没有参加此次选举 ,也就无法进行投票,但是在此轮选举中,节点 D 依然获得了半数以上的选票,故成为新任期(Term=4)的 Leader节点,并开始向其他 Follower节点发送心跳消息, 如图 12 所示。

image

​ 当节点 A 恢复之后,会收到节点 D 发来的心跳消息,该消息中携带的任期号 (Term=4)大 于节点 A 当前记录的任期号( Term=3),所以节点 A 会切换成 Follower 状态 。 在 Raft 协议中, 当某个节点接收到的消息所携带的任期号大于当前节点本身记录的任期号,那么该节点会更新自身记录的任期号,同时会切换为 Follower 状态并重置远举计时器,这是 Raft 算法中所有节点都要遵守的一条准则。

最后请读者考虑一个场景 :如果集群中选出的 Leader 节点频繁崩溃或是其他原因导致选举频繁发生 ,这会使整个集群中没有一个稳定的 Leader节点,这样客户端无法与集群中的 Leader 节点正常交互,也就会导致整个集群无法正常工作。

Leader选举是 Raft算法中对时间要求较为严格的一个点, 一般要求整个集群中的时间满足 如下不等式 :

广播时间 « 选举超时时间 « 平均故障间隔时间

​ 在上述不等式中,广播时间指的是从 一个节点发送心跳消息到集群中的其他节点并接收响 应的平均时间 ;平均故 障间隔时间就是对于一个节点而言,两次故障之间的平均时间 。 为了保 证整个 Raft集群可用,广播时间必须比选举超时时间小-个数量级,这样 Leader节点才能够发 送稳定的心跳消息来重置其他 Follower 节点的选举计时器,从而防止它们切换成 Candidate 状 态,触发新一轮选举。在前面的描述中也提到过,选举超时时间是一个随机数,通过这种随机 的方式,会使得多个 Candidate 节点瓜分选票的情况明显减少,也就减少了选举耗时。另外,选 举超时时间应该 比平均故障间隔时间小几个数量级,这样 Leader 节点才能稳定存在,整个集群 才能稳定运行。 当 Leader节点崩溃之后,整个集群会有大约相当于选举超时的时间不可用,这 种情况占比整个集群稳定运行的时间还是非常小的。

​ 广播时间和平均故障间隔时间是由网络和服务器本身决定的 ,但是选举超时时间 是可以由 我们自己调节的。一般情况下,广播时间可以做到 0.5ms~50ms,选举超时时间设置为 200ms~ 1s 之间,而大多数服务器的平均故障间隔时间都在几个月甚至更长,很容易满足上述不等式的 时间 需求 。

网络分区

​ 在一个集群中,如果有部分节点的网络发生故障,与集群中另 一部分节点的连接中断,就会出现网络分区, 假设集群中有 A、 B、 C、 D、 E 五个节点,其中节点 A、 B 相互之间网络连通,节点 C、 D、 E 相互之 间网络连通,但是这两部分节点之间出现网络故障,这就形成了网络分区。

​ 假设集群中节点 A 是 Leader 节点,它会向其他四个节点发送 Append Entries 消息和心跳消息, 当 出现网络出现分区时,节点 A 的心跳消息只有节点 B 才能收到,而集群中的其他节点收不到,然而节点 A 发往节点 C、 D、 E 的消息由于网络分区,并不会抵达节点 C、 D、 E。

​ 随着时间的流逝,集群中与 Leader节点隔离的网络分区 C、 D、 E 中,会率先有一个节 点的选举计时器( election timer)超时,这里假设该节点是 E,此时的节点 E 就会切换成 Candidate 状态并发起下 一轮选举,如下图所示 。 由于网络分区,当前集群中只有节点 C、 D 能够 收到节点 E 的选举请求,这里假设节点 C、 D 都会将选 票投给节点 E,如图下所示 。

image

到此为止,节点 E 在此次选举中收到了得到三票(其中包括它本身的一票),达到集群半数以上,所以节点 E 成为新任期(Term = 2)的 leader 节点,如下图所示:

image

​ 当网络故障被修复时,上述的网络分区也就会消失,此时节点 A (任期 Term = 1 的 Leader 节点〉发送的心跳消息会被节点 C、 D、 E 接收到(图中虽然省略了这些由于网络分区而 无法送达的心跳消息,但实际上节点 A 依然认为自己是 Leader 节点,在发送心跳消息时也会向 节点 C、 D、 E 发送心跳消息),但是这些心跳消息中携带的 Term 值小于当前 C、 D、 E 节点的 Term 值 ,会被 C、 D、 E 节点忽略。同时,节点 E (Term=2 任期的 Leader 节点〕发送的心跳消 息会被节点 A、 B 接收到(上图中同样省略了这些无法送达的心跳消息),不同的是,这些 心跳消息携带的 Term 值大于当前 A、 B 节点的 Term 值,所以节点 A、 B 会切换成 Follower 状 态,这样整个集群中的 Leader 节点依然是节点 E。

​ 如果网络分区时, Leader 节点划分到节点较多的分区中,如上图所示, 此时节点较少的分区中,会有节点的选举计时器超时,切换成 Candidate状态并发起新一轮的选 举。但是由于该分区中节点数不足半数,所以无法选举出新的 Leader 节点 。 待一段时间之后, 该分区中又会出现某个节点的选举计时器超时,会再次发起新 一 轮的选举,循环往复,从而导 致不断发起选举, Term 号不断增长 。

​ 在 Raft协议中对这种情况有一个优化,当某个节点要发起选举之前,需要先进入一个叫作 PreVote 的状态,在 该状态下,节点会先尝试连接集群中的其他节点,如果能够成功连接到半数 以上的节点,才能真正发起新一轮的选举。 通过这种方式就可以解决上述的问题 。

​ 当网络分区恢复时,集群中存在 新旧两个 Leader 节点 A 和 E,其中节点 E 的 Term 值较 高 ,会成为整个集群中的 Leader 节点 。 但是由于之前的网络分区,节点 A、B 的本地 Log 中可能存在未提交的日志记录,此时节点 A 和 B 会回滚未提交的日志记录,并重新复制新 Leader 节 点的日志。

日志复制

​ 通过上一节介绍的 Leader选举过程,集群中最终会选举出 一个 Leader节点,而集群中剩余 的其他节点将会成为 Follower 节点 。 Leader 节点除了向 Follower 节点发送心跳消息,还会处理 客户端的请求,并将客户端的更新操作 以消息( Append Entries 消息)的形式发送到集群中所有 的 Follower 节点。当 Follower 节点记录收到的这些消息之后,会向 Leader 节点返回相应的响应 消息。 当 Leader节点在收到半数以上的 Follower节点的响应消息之后,会对客户端的请求进行 应答 。 最后, Leader 会提交客户端的更新操作,该过程会发送 Append Entries 消息到 Follower 节点,通知 Follower节点该操作己经提交,同时 Leader节点和 Follower节点也就可以将该操作 应用到自己的状态机中 。

​ 上面这段描述仅仅是 Raft 协议中日志复制部分的大致流程,下面我们依然通过一个示例描述该过程,为了方便描述,我们依然假设当前集群中有三个节点 (A、 B、 C),其中 A 是 Leader 节点, B、 C 是 Follower 节点, 此时有一个客户端发送了一个更新操作到集群,如图 2-1 所示。前面提到过,集群中只有 Leader节点才能处理客户端的更新操作,这里假设客户端直接 将请求发给了节点 A。当收到客户端的请求时,节点 A 会将该更新操作记录到本地的 Log 中, 如图 2-1 所示。

image

​ 之后,节点 A 会向其他节点发送 Append Entries 消息,其中记录 了 Leader 节点 最近接收到 的请求日志,如图 2-1 所示。集群中其他 Follower节点收到该 Append Entries 消息之后, 会将该操作记录到本地的 Log 中,并返回相应的响应消息,如图 2-2 所示。

image

​ 当 Leader 节点收到半数以上的响应消息之后,会认为集群中有半数以上的节点已经记录了该更新操作, Leader 节点会将该更新操作对应的日志记录设置为己提交(committed), 并应用 到自身的状态机中 。同 时 Leader 节点还会对客户端的请求做出响应,如图 2-3 所示。 同时, Leader 节点也会向集群中的其他 Follower 节点发送消息,通知它们该更新操作己经被 提交, Follower节点收到该消息之后,才会将该更新操作应用到自己的状态机中,如图 2-3 所示 。

image

​ 在上述示例的描述中我们可以看到,集群中各个节点都会维护一个本地 Log 用 于记录更新 操作, 除此之外,每个节点还会维护 commitlndex和 lastApplied两个值,它们是本地 Log 的索引值,其中 commitlndex 表示的是当前节点已知的、最大的、己提交的日志索引值, lastApplied 表示的是当前节点最后一条被应用到状态机中的日志索引值。当节点中的 commitlndex 值大于 lastApplied值时,会将 lastApplied 加 l,并将 lastApplied对应的日志应用到其状态机中。

​ 在 Leader节点中不仅需要知道自己的上述信息,还需要了解集群中其他 Follower节点的这 些信息,例如, Leader 节点需要了解每个 Follower 节点的日志复制到哪个位置,从而决定下次 发送 Append Entries 消息中包含哪些日志记录。为此, Leader 节点会维护 nextlndex[]和 matchlndex[]两个数组,这两个数组中记录的都是日志索引值,其中 nextlndex[]数组记录了需要 发送给每个 Follower 节点的下一条日志的索引值, matchlndex[]表示记录了己经复制给每个 Follower 节 点的最大的日志索引值。

​ 这里简单看一下 Leader 节点与某一个 Follower 节点复制日志时,对应 nextlndex 和 matchlndex 值的变化,Follower 节点中最后 一条日志的索引值大于等于该 Follower 节点对应的 nextlndex 值,那么通过 Append Entries 消息发送从 nextlndex 开始的所有日志。之后, Leader 节点会检测该 Follower 节点返回的相应响应,如果成功则更新相应该 Follower 节点对应的 nextlndex 值和 matchlndex 值 。如果因为日志不一致而失败,则减少 nextlndex 值重试。

​ 下面我们依然通过一个示例来说明 nextlndex[] 和 matchlndex[] 在日志复制过程中的作用 ,假 设集群现在有三个节点, 其中节点A是Leader节点(Term=1), 而Follower节点C因为宕机导 致有一段时间未与Leader节点同步日志。此时, 节点C的Log中并不包含全部的己提交日志, 而只是节点A的Log的子集, 节点C故障排除后重新启动, 当前集群的状态如图所示(这 里只关心 Log、 nextlndex[]、 matchlndex[],其他的细节省略, 另外需要注意的是, 图中的 Term = 1 表示的是日志发送时的任期号, 而非当前的任期号)。

image

​ A作为 Leader节点, 记录了 nextlndex口和 matchlndex[],所以知道应该向节点 C发送哪些日志, 在本例中, Leader节点在下次发送 Append Entries消息时会携带 Index=2 的消息(这里 为了描述简单,每条消息只携带单条日志 , Raft 协议采用批量发送的方式,这样效率更高) , 如 图 2-4 所示。当节点C收到AppendEntries消息后, 会将日志记录到本地Log中, 然后向 Leader 节点返回追加日志成功的响应 , 当 Leader 节点收到响应之后 , 会递增节点 C 对应的 nextlndex和 matchlndex, 这样 Leader节点就知道下次发送日志的位置了, 该过程如图 2-5 所示 。

image

在上例中, 当 Leader 节点并未发生过切换,所以 Leader 节点始终准确地知道节点 C 对应 nextlndex 值和 matchlndex 值 。如果在上述示例 中, 在节点 C 故障恢复后 , 节点 A 宕机后重启,并且导致节点 B 成为新任 期(Term=2) 的 Leader 节点,则此时节点 B 并不知道旧 Leader 节点中记录的 nextlndex[] 和 matchlndex[] 信息 , 所 以新 Leader 节点会重置 nextlndex[] 和 matchlndex[], 其中会将 nextlndex[] 全部重置为其自身 Log的最后一条己提交日志的 Index值,而 matchlndex[] 全部重置为 0,如 图 2-6 所 示 。

image

​ 随后,新任期中的 Leader节点会向其他节点发送 AppendEntries消息,如图 2-7 所示, 节点 A 己经拥有了 当前 Leader的全部日志记录,所 以会返回追加成功的响应并等待后续的日志 , 而节点 C 并没有 Index=2 和 Index=3 两条日志,所以返回追加日志失败的响应,在收到该响应后, Leader节点会将 nextindex前移,如图 2-8 所示。

image

​ 然后新 Leader 节 点会再次尝试发送 Append Entries 消息,循环往复,不断减小 nextlndex 值,直至节点 C 返回追加成功的响应,之后就进入了正常追加消息记录的流程,不再赘述。

了解了 Log 日志及节点中基本的数据结构之后,请读者回顾前面描述的选举过程,其中 Follower 节点的投票过程并不像前面描述的那样简单(先收到哪个 Candidate 节 点的选举请求, 就将选票投给哪个 Candidate 节点), Follower 节点还需要比较该 Candidate 节点的日志记录与自 身的日志记录,拒绝那些日志没有自己新的 Candidat巳节点发来的投票请求,确保将选票投给包 含了全部己提交 Commited 时 )日志记录的 Candidate 节点。这也就保证了己提交的日志记录不 会丢失 : Candidate 节点为了成为 Leader 节点,必然会在选举过程中向集群中半数以上的节点发送选举请求,因为己提交的日志记录必须存在集群中半数以上的节点中,这 也就意味着每一条 己提交的日志记录肯定在这些接收到节点中的至少存在 一份 。 也就是说,记录全部己提交日志 的节点和接收到 Candidate 节点的选举请求的节点必然存在交集,如图 2-9 所示。

image

​ 如果 Candidate节点上的日志记录与集群中大多数节点上的日志记录一样新,那么其日志一 定包含所有己经提交的日志记录,也就可以获得这些节点的投票并成为 Leader。

​ 在比较两个节点的日志新 旧时 , Raft 协议通过 比较两节点日志中的最后一条日志记录的索 引值和任期号,以决定谁的日志比较新 : 首先会比较最后一条日志记录 的任期号,如果最后的日志记录的任期号不同,那么任期号大的日志记录比较新:如果最后 一条日志记录的任期号相同,那么日志索引较大的比较新 。

结术语

​ 本文只是介绍了 Raft 协议的 Leader 选举、日志复制过程,略去了部分细节,关于日志压缩、客户端与集群交互等细节,感兴趣的读者可阅读 raft 论文或其它文档进行更详细的了解。

参考文档

Raft 论文

​ ETCD 技术内幕