掘金 后端 ( ) • 2024-04-18 18:13

  Raft 是一种可靠,但相对简单的分布式一致性算法,被应用于 Etcd、RabbitMQ 等。其设计的主要动机之一是在不影响性能和正确性的前提下增强算法的可理解性。

  Raft 分离了一致性算法的关键元素:leader 节点选举、日志复制、安全性,这使得 Raft 算法更加模块化以及更易于理解;同时也方便了对算法中特定的元素进行优化以及定制化。

  Raft 首先会进行 leader 节点选举,之后集群中节点之间的日志复制则完全由 leader 节点负责。所有来自客户端的请求最终都由 leader 节点进行响应(其他节点的客户端请求也会被重定向到 leader 节点)。

  leader 节点负责响应所有来自客户端的请求,这可能会限制 Raft 算法的性能。因为集群中 leader 节点只能有一个,当客户端请求量尤其是写操作的请求量很大时,可能会对 leader 节点的负载造成较大的压力,影响吞吐。

  一些应用场景中可以对读操作进行负载均衡,但写操作只能请求 leader 节点,大量写操作还是会对 leader 节点的负载造成压力;另外,水平扩容配合负载均衡也可以降低读操作的压力,但同时会增加节点之间通信的开销以及节点之间日志复制的延时,这也会影响写操作的性能。

⒈ 节点状态

  Raft 中节点只有三种状态:leader(领导者)、follower(追随者)、candidate(候选者)。任何时间,集群中节点的状态只能是上述三种状态之一。通常情况下,集群中只有一个 leader 节点,其他全部是 folower 节点。

  • leader 节点负责响应客户端的请求以及将日志复制到 follower 节点。
  • follower 节点只会被动响应来自 leader 节点以及 candidate 节点的请求
  • 当集群中当前 leader 节点失效时,检测到 leader 节点失效的 follower 节点会成为 candidate 节点,准备发起投票竞选新的 leader 节点。

candidate 只是节点的一个临时状态。如果一个 candidate 节点获得多数节点投票,则它会成为新的 leader 节点;反之,则它又会变回 follower 节点。

⒉ 时间

image.png

  Raft 将时间分割成了任意长度的时间间隔 term。这些 term 以连续的单调递增的整数进行编号,每次新的 leader 节点选举标志着一个新的 term 的开始,这个新的 term 一直会持续到这个 leader 节点失效为止。

  在有些情况下可能会出现多个 candidate 节点都没有得到多数节点的投票,导致本次 leader 节点选举失败。此时,系统会开始一个新的 term 重新进行 leader 节点选举。

  在 Raft 算法中,term 的作用相当于一个逻辑时钟。系统在选出新的 leader 节点之后,新的 term 值也会随着节点之间的消息交互传递给其他节点。系统中的所有节点都会在本地维护一个 term 值,当一个节点发现本地维护的 term 值小于消息中的 term 值时会更新本地的 term 值。如果 leader 节点或 candidate 节点发现本地维护的 term 值已经过期,则会将节点状态变为 follower。另外,任何 term 值过期的请求都会被节点拒绝。

⒊ 消息交互

  在 Raft 算法中,系统中的节点之间通过 RPC 进行消息交互。Raft 使用两种类型的 RPC 消息进行交互:candidate 节点在发起 leader 节点选举投票时使用 RequestVote 类型的 RPC 消息;AppendEntries 类型的 RPC 消息则是在 leader 节点进行日志复制以及心跳检测时使用。

RequestVote RPC
  参数:
   term     candidate 节点当前的 term 值
   candidateId candidate 节点的 ID
   lastLogIndex candidate 节点最后一条日志条目的索引
   lastLogTerm  candidate 节点最后一条日志条目的 term 值

  返回:
   term     follower 节点当前的 term 值
   voteGranted  如果为 TRUE 则表示 follower 节点投票给 candidate

AppendEntries RPC
  参数:
   term     leader 节点当前的 term 值
   leaderId    leader 节点的 ID
   prevLogIndex leader 节点日志中最后一条日志条目的索引
   prevLogTerm leader 节点日志中最后一条日志条目的 term 值
   entries[]    要复制的日志条目,日志条目为空表示发送的心跳检测;为了提高传输效率,可以一次传多条日志条目
   leaderCommit leader 节点最新一条提交执行的日志条目的索引

  返回:
   term     follower 节点当前的 term 值
   success    如果 follower 节点中最新的日志条目与 prevLogIndex 和 prevLogTerm 所对应的 leader 节点中的日志条目匹配,则返回 TRUE

如果 follower 节点中最新的日志条目的索引小于 prevLogIndex,那么 follower 的返回中除了将 success 设为 FALSE,还可以额外将 follower 节点当前日志中最新的日志条目的索引一并返回,方便后续补齐缺少的日志条目

⒋ leader 节点选举

  在 Raft 算法中,leader 节点会周期性的向所有 follower 节点发送心跳检测消息,如果有 follower 节点在一定时间(election timeout)内没有收到心跳检测消息,则会触发新的 leader 节点选举操作。

  在 follower 节点触发新的选举之前,该节点首先会将自身的状态变成 candidate 并增加节点本地维护的 term 值,另外,节点还会为自己投票并向系统中的其他节点发送 RequestVote 消息。

  如果一个 candidate 节点收到了多数节点的投票,则该节点会成为新的 leader 节点,之后这个新的 leader 节点继续向系统中的其他节点周期性的发送心跳检测消息。在 candidate 节点等待其他节点投票的过程中可能会收到一个自称 leader 的节点发送的 AppendEntries 消息,如果消息中的 term不小于当前 candidate 节点本地维护的 term 值,则 candidate 节点放弃本次选举并重新变成 follower;反之,则 candidate 节点拒绝响应该消息,继续等待其他节点投票。

  系统中可能会存在多个节点同时发起 leader 选举,如果这些 candidate 节点本地维护的 term 值都相同,则 follower 节点只会响应最先到达的那个 RequestVote 请求。如果这些 candidate 节点都没有得到多数节点的投票,则在请求超时之后会重新进行下一轮的 leader 选举投票(同时增加各自本地维护的 term 值)。为了避免这种情况的出现,Raft 会为每次 leader 选举随机指定一个超时时间(150 ms ~ 300 ms 之间),这样,最先超时的那个 candidate 会最先发起新的一轮的 leader 选举,最终成为新的 leader 节点。

⒌ 日志复制

   日志是分布式系统中维护状态一致性的关键数据。leader 节点在接收到客户端请求后会将请求转化为日志条目(Log Entry),存储到本地日志中。日志条目中除了存储本次请求要执行的命令之外,还会存储 leader 节点当前的 term 值以及日志条目在日志中的索引位置。之后 leader 节点会通过 AppendEntries 消息将这些日志条目发送到所有的 follower 节点。

  follower 节点在收到日志复制的消息后,首先检查消息中的 term 值,其值不能小于 follower 节点本地维护的 term 值。其次通过消息中的 prevLogIndexprevLogTerm 确定本地存储的消息是否为最新。如果确定本地存储的消息为最新,则 follower 节点会将消息中的日志条目复制到本地;反之,则不进行日志复制,而且 follower 节点还可以将本地最新的日志条目的索引返回给 leader 节点,以便后续补齐缺少的日志信息。

  如果不同节点之间的两条日志条目包含相同的索引以及 term 值,则认为这两条日志条目相同,并且这两条日志条目之前的所有日志条目都相同。

  待系统中的多数节点都完成日志复制之后,leader 节点会将日志条目中的命令提交执行。之后,leader 节点会将提交执行的消息通知到所有的 follower 节点,follower 节点也会在本地将相应的命令提交执行。

⒍ 安全性

  在进行 leader 节点选举时,candidate 节点发送的 RequestVote 消息中会携带节点本地维护的日志中最新的日志条目的索引以及 term 值。其他节点在收到 RequestVote 消息时首先会检查消息中携带的索引以及 term 信息,只有消息中的索引以及 term 值不小于节点本地最新的日志条目的索引以及 term 值,节点才会进行投票,否则节点拒绝投票。

  leader 节点的选举机制保证了在任意时刻,系统中最多只会有一个 leader 节点。另外,leader 节点中的日志条目只能追加,既不能删除也不能覆盖。

  Raft 保证了客户端请求中的命令在所有节点中提交的顺序是一致的,这保证了所有节点中数据以及状态的最终一致性。