References
1. Raft 是什么
1.1 目标: 复制 Log
在讲解 Raft 协议的具体行为之前我们需要明白 Raft 的目标是什么?在一些情况下我们需要保证分布式集群中的机器拥有相同的数据,以确保在 Leader 节点宕机的情况下,其他节点能够作为新的 Leader 被选举出来处理请求。这里产生一个问题,即我们如何才能确保集群中的机器拥有相同的数据呢?
Raft 提供了一种行之有效的方式:即将 Leader 节点将要执行的命令,通过日志复制的方式同步到其他的节点,再由状态机按照日志的顺序执行日志中的每一条命令,就能够确保 Leader 节点上的数据与其他节点上的数据保持一致。这里提到的状态机通常指的是一个输入输出程序或应用。所以 Raft 的目的以及运作机制都在于此:
- 复制 Log => 复制状态机 => 同步数据
这里简要介绍下这一过程:客户端将要执行的命令传递给集群中的 Leader,假设命令是 X,那么 X 首先会被 Leader 集群,然后会通过日志复制的方式(通常是一次 RPC 调用)同步到其他的节点,并被其他节点作为日志记录到本地。一旦 X 被安全地复制到日志中,那么它们就能被发送到状态机供执行。当状态机完成了 X 的执行,就会将结果返回给客户端。
可以注意到只要各个节点上的 Log 是相同的,各个服务器上的状态机就能以相同的顺序执行相同的命令,这样它们执行的结果也都是一样的。所以共识性模块(上图中的 Consensus Module
)的任务就是管理这些日志,并保证它们正确的在集群内复制并且决定何时将命令传送给状态机才是安全的。
此外,Raft 设计之初也有为了容灾的目的。Raft 不需要所有的节点在任何时候都处于运行状态,实际上只要在大多数服务器(一般是 > 1/2 的节点)存活的状态下能继续正常运行和相互通信就可以。
1.2 实现共识的方式
想要实现共识性算法主要有两种方式:
- 对称式或无主式:在这种方式下,所有的节点的角色相同,拥有同等的权力,任何时候的行为几乎都是一样的,客户端可以与任何一个节点进行通信;
- 非对称式或领导式:集群内节点在任何时候都不是对等的,只有其中的一台服务器是 Leader,Leader 负责集群的所有操作,其他的节点只是简单地服从 Leader 发出的指令,在这种系统下,客户端永远与 Leader 通信,只有 Leader 才与其他的节点发送通信。
Raft 就是基于领导式的协议。它将共识性算法的问题分解成两类不同的问题:
- 一种是在 Leader 正常运行下,进行的普通操作;
- 另一种是在 Leader 崩溃时,对 Leader 进行重新选举。
通常来说,基于领导者的方式要比无领导者的方式简单,因为无需担心不同服务器间会出现冲突,只须关心领导者发生变化的情况。
1.3 Raft 协议概览
Raft 算法将分布式一致性分解为多个子问题:
- Leader 选举(Leader election)
- Leader 变更(Leader Changes)
- 日志复制(Log Replication)
- 日志压缩(Log Compaction)
- 安全性(Safety)
- 客户端协议(Client Protocol)
- …
2. Leader 选举(Leader Election)
对于 Raft 来说,想要进行正常的日志复制以及对异常日志的处理,都需要有一个 Leader 节点。所以我们首先先来了解 Raft 中 Leader 节点的选举流程。这一块内容只依赖文字描述很难形成详细理解,建议读者结合 Raft Leader Election 进行阅读。
2.1 节点状态
任意情况,Raft 中的节点都处于以下三种状态之一:
- Leader:集群中的领导者。负责接受客户端发送的写请求、将请求命令同步到其他节点(即日志同步)、;
- Follower:集群中的跟随者。负责 Leader 接受更新请求,然后写入本地日志文件,在 Leader 宕机后成为 Candidate,作为 Leader 的备份存在;
- Candidate:Leader 宕机后集群中的候选者。负责发起选举投票。如果 Follower 节点在一段时间内没有收到 Leader 的心跳,则判断 Leader 可能已经故障,此时启动选主过程,副本将转变为 candidate 状态,直到选主结束。
下图是集群中角色状态的转变图,这里可以先做大致的了解:
2.2 任期(Term)
Raft 的中另外一个重要概念是 Term,意为任期。Raft 将整个运行过程的时间分为任意时长的任期。每个任期 Raft 只做两件事:
- 选出 Leader 节点;
- 在 Leader 节点的领导下,完成日志同步或日志一致性修复等操作。
对于 Raft 中的每一个任期,最多只能有一个 Leader。当然某些任期可能还会没有 Leader 的存在,这是由于可能存在没有 Candidate 获得集群内多数节点选票而造成的,当这种情况出现时,系统将会进入新的任期并尝试重新选举。
此外,在 Raft 系统的所有节点都保持着一个当前任期的值,这个信息一般存在于节点的可靠媒介中(如硬盘),这样就能在服务器崩溃之后得以重启并恢复。
任期这个概念十分重要,它使 Raft 中的节点可以判断过期的信息。例如一台认为当前的任期为 2 的节点与另一认为当前任期号为 3 的节点进行通信,那么 Raft 就能知道来自于任期 2 的信息是过期的。所以我们将会看到在某些情况下,会使用到任期来检查并消除过期的信息。
2.3 心跳与超时时间
在 Raft 中,当一个节点加入集群后它首先将以 Follower 的角色运行,这个时候该节点期望收到来自 Leader 或 Candidate 的 RPC 消息。而作为 Raft 集群中的 Leader,也必须定时地发送心跳消息给集群中的其他节点,以告知其他节点当前 Leader 节点还正常运行,保证其 Leader 的地位。
一旦 Follower 节点在超时时间(electionTimeout,通常为 100~500ms)内没有收到来自其他节点的 RPC 消息,那么该 Follower 节点会认为当前集群没有 Leader 节点,且没有 Candidate 正在进行选举,从而将转变成为 Candidate,发起选举。
2.4 选举的流程
上面提到当一个 Follower 节点在超时时间后没有收到来自其他节点的 RPC 消息后,它就将转变为 Candidate 节点,发起选举。那么这个过程大概如下所示:
- 增加当前的任期号,+1;
- 随后将自己从 Follower 状态转换到 Candidate 状态;
- 为自己投票(Raft 要求 Candidate 需要获得多数节点,即超过半数节点的投票才能成为 Leader,不要忽略节点自身的这一票);
- 发送
RequestVote
RPCs 消息给集群内其他节点,该 RPC 消息会超时重试,直到以下三种情况:- 收到超过半数节点的投票:
- 成为 Leader;
- 定时向集群内其他节点发送
AppendEntries
心跳消息;
- 收到来自其他已认证 Leader 节点的心跳消息:
- 将自己切换为 Follower 状态,追随该 Leader 节点
- 没有选出 Leader(在超时时间后,没有收到来自超过半数节点的选票):
- 将任期号往上加 1,开启新一轮选举,重复上述流程。
- 收到超过半数节点的投票:
2.5 安全性 & 可用性
-
安全性:对于 Raft 来说,每一个任期(Term)最多只能有一个 Leader,否则将产生脑裂。对此 Raft 对选举策略做了以下要求保证这一点:
-
不允许两个不同的 Candidate 同时在同一个任期内获得超过半数的选票;
-
每一节点只能投票一次并将投票信息记录到磁盘;
节点一旦投出选票,将拒绝来自其他 Candidate 的任何请求。为了实现这种机制,节点需要保证将自己的投票信息存储到磁盘,这样就能
-