莫那·鲁道

惯于闲看花飞花落, 心念天际云卷云舒.

Coder, 欢迎留言 😆.


GitHub

Raft 配置变更 Configuration changes

背景

什么是配置变更?

说白了,就是动态增减服务器。

假设,一个 Raft 服务器集群的数量刚开始是 3 台,过了 2 个月,由于要搞类似 618,双11 大促,领导要增加系统可用性,那么就要增加到 5 台。

如果增加到 5 台,就带来了配置的变化,首当其冲的,就是 “服务器数量” 这个配置的变化,例如这个配置叫做 “server_count = 3”, 你需要将配置改成 “server_count = 5”。同时,每台服务器还需要知道其他 4 台服务器的 ip + port,这个配置实际上也是需要变化的。

并且,你需要在不停机的情况下,将新的 2 台服务器增加,并且将这些配置应用到另外 3 台旧的服务器上。

这个时候,你该怎么做?

假设是我,我会先将新的 2 台服务器配置完毕(新的配置)再启动。然后,将新的配置发送到旧的 3 台服务器。那 3 台服务器如果成功收到并提交了这个配置,使这个配置生效,那么整个集群的状态就一致了。

很完美。

但是,分布式肯定没有这么简单。假设,在成功启动 2 台新服务器后,老的集群发生了故障,重新开始选举,这个时候,是怎么样子的?

看下图:

图 1

上图中,一共有 5 台服务器,S1,S2,S3 代表旧的服务器,S4,S5 代表着新的服务器。

绿色代表旧的配置,蓝色代表新的配置。

我们模拟一下:

  1. 我们先启动了 S4 和 S5,成功启动。
  2. 然后我们将新的配置发送到 S1,S2,S3 中,试图让他们应用新的配置。
  3. 在某个时刻,S3 成功应用新的配置,同时,旧的系统发生了故障,并开始选举。
  4. 5 台服务器一起开始选举,由于 S1 和 S2 还没有应用新的配置,所以,S1 和 S2 仍然以为只有 3 台服务器,并且在得到 2 张选票后,成功选出一个领导者
  5. 而 S3,S4,S5 应用了新的配置,并且获得 3 台服务器的认可,也成功选出了领导人
  6. 此时,整个系统出现了 2 个领导人。

那么,是哪里出现了问题呢?

根本原因在于,在同一时刻,有 2 份配置生效了!!!

自然就可以选出 2 个领导人。

所以,要防止这个问题发生,必须不能让 2 份配置同时生效。

Raft 的实现

Raft 是怎么做的呢?

Raft 使用了一种 2 阶段提交的方案。

具体见下图:

图 2

第一阶段:

  • 发送新配置到旧服务器的 leader。leader 不会直接存储新配置,而是存储旧配置 + 新配置。同时,一旦这个配置被提交(成功同步到新旧集群的大部分跟随者中),那么,所有服务器必须用这个配置来做决定。也就是说,旧配置失效了,不能拿旧配置做任何决定,同时,此时的系统状态是一致的。这个状态称之为共同一致

第二阶段:

  • 旧配置 + 新配置 被成功提交,这个时候,leader 会创建一条新配置 复制到 followers 中。从而完成配置变更。

从上图和上文的解释可以看出,这里不会出现新配置旧配置同时出现的场景。因为他们都被融合在了 旧配置 + 新配置 中去了。即将两个配置融合成一个配置。

那么,图 1 的问题——同时有 2 个 leader 的问题就解决了。

思考:共同一致能确保没有问题吗?

简化起见,我将 新配置 + 旧配置 称之为 共同配置。 为了阅读方便,我将 旧配置 称之为 old 配置,将新配置 称之为 new 配置

从图 2 可以看出,一共有 2 个关键的节点:

  1. 提交 “共同配置”
  2. 提交“new 配置”

如果这个两个地方出现问题了,怎么办?

假设: 共同配置 提交失败,会怎么样?例如提交的时候,老的 leader 崩溃了。

这要分 2 种情况来看: 1.新 leader 已经收到 “共同配置”,那么新 leader 将继续进行配置变更操作。

  1. 新 leader 没有收到 “共同配置”,自然使用 old 配置。

假设:new 配置提交失败,会怎么样?例如提交的时候,leader 崩溃了。

这要看 new 配置有没有同步到大部分节点。

  1. 假设已经同步到大部分节点,则集群使用“新配置” —— 完成配置变更。
  2. 假设没有同步到大部分节点,则集群使用 “共同配置”,继续进行配置变更。
另一个问题:共同一致阶段,如果 leader 崩溃,使用什么策略进行选举?

答:如果 共同配置 被应用了,那么,由于领导人完全特性(如果某条日志在某个任期号中已经被提交,那那个条目必然出现在更大任期号的所有领导人中),新的 leader 必然拥有 共同配置。

如下图:

最后,Raft 论文提出的 3 个问题

1. 新的服务器在初始化时,没有存储任何日志条目。

这个带来的问题是:由于没有存储任何日志条目,那么,就需要时间来补充日志条目,这实际上,是会影响可用性的。

Raft 使用了一种方法来避免:这个阶段的服务器是没有投票权力的。只有当器补充完日志条目了,才会加入集群。

2. 集群的领导人可能不是新配置的一员

什么意思? 答: 当旧集群的 leader 提交了新配置,Leader 需要变成 follower。

为什么?

答:当新的配置生效时,旧 leader 还使用的老的配置,例如新配置的服务器数量是 5 台,而老的 leader 仍然应用的是 3 台服务器。

新集群应该使用新的配置重新进行选举(不然使用新配置干嘛?)。

3. 移除不在新配置中的服务器可能会扰乱集群

当移除他们时,他们会重新进行选举,导致当前 leader 回退到 follower 状态。虽然新的 leader 会被选出来,但被移除的服务器会再次请求重新选举,循环反复,影响可用性。

Raft 的解决方式:

  1. 当节点确认当前领导人存在时,则忽略请求投票的 RPC 请求。
  2. 当节点在最小选举超时时间里收到请求投票请求,他不会更新当前的任期号或者投出选票(从而扰乱集群)。

参考

英文 paper pdf 地址

Raft paper 中文翻译 —— 寻找一种易于理解的一致性算法(扩展版)

Raft 作者讲解视频

Raft 作者讲解视频对应的 PPT

一个简单的讲解 Raft 协议的动画