Raft算法详解(三)解决成员变更的问题

在日常工作中,可能会遇到服务器故障的情况,这时就需要替换集群中的服务器。如果 遇到需要改变数据副本数的情况,则需要增加或移除集群中的服务器。总的来说,在日常工 作中,集群中的服务器数量是会发生变化的。

讲到这儿,也许你会问:“Raft 是共识算法,对集群成员进行变更时(比如增加 2 台服务器),会不会因为集群分裂,出现 2 个领导者呢?”

在我看来,的确会出现这个问题,因为 Raft 的领导者选举,建立在“大多数”的基础之 上,那么当成员变更时,集群成员发生了变化,就可能同时存在新旧配置的 2 个“大多数”,出现 2 个领导者,破坏了 Raft 集群的领导者唯一性,影响了集群的运行。

而关于成员变更,不仅是 Raft 算法中比较难理解的一部分,非常重要,也是 Raft 算法中 唯一被优化和改进的部分。比如,初实现成员变更的是联合共识(Joint Consensus), 但这个方法实现起来难,后来 Raft 的作者就提出了一种改进后的方法,单节点变更 (single-server changes)

今天除了了解成员变更问题的本质之外,还会讲一下如何通 过单节点变更的方法,解决成员变更的问题。学完本讲内容之后,你不仅能理解成员变更的 问题和单节点变更的原理,也能更好地理解 Raft 源码实现,掌握解决成员变更问题的方 法。

在开始今天内容之前,我先介绍一下“配置”这个词儿。因为常听到有人说,自己不理解 配置(Configuration)的含义,从而不知道如何理解论文中的成员变更。

的确,配置是成员变更中一个非常重要的概念,我建议你这么理解:它就是在说集群是哪些 节点组成的,是集群各节点地址信息的集合。比如节点 A、B、C 组成的集群,那么集群的 配置就是[A, B, C]集合。

理解了这一点之后,咱们先来看一道思考题。

假设我们有一个由节点 A、B、C 组成的 Raft 集群,现在我们需要增加数据副本数,增加 2 个副本(也就是增加 2 台服务器),扩展为由节点 A、B、C、D、E, 5 个节点组成的新集 群:

在这里插入图片描述

那么 Raft 算法是如何保障在集群配置变更时,集群能稳定运行,不出现 2 个领导者呢?带 着这个问题,我们正式进入今天的学习。

老话说得好,“认识问题,才能解决问题”。为了帮你更好地理解单节点变更的方法,我们 先来看一看,成员变更时,到底会出现什么样的问题?

成员变更的问题

在集群中进行成员变更的大风险是,可能会同时出现 2 个领导者。比如在进 行成员变更时,节点 A、B 和 C 之间发生了分区错误,节点 A、B 组成旧配置中的“大多 数”,也就是变更前的 3 节点集群中的“大多数”,那么这时的领导者(节点 A)依旧是 领导者。

另一方面,节点 C 和新节点 D、E 组成了新配置的“大多数”,也就是变更后的 5 节点集 群中的“大多数”,它们可能会选举出新的领导者(比如节点 C)。那么这时,就出现了同 时存在 2 个领导者的情况。

在这里插入图片描述

如果出现了 2 个领导者,那么就违背了“领导者的唯一性”的原则,进而影响到集群的稳 定运行。你要如何解决这个问题呢?

因为我们在启动集群时,配置是固定的,不存在成员变更,在这种情况下,Raft 的领导者 选举能保证只有一个领导者。也就是说,这时不会出现多个领导者的问题,那我可以先将集 群关闭再启动新集群啊。也就是先把节点 A、B、C 组成的集群关闭,然后再启动节点 A、 B、C、D、E 组成的新集群。

在我看来,这个方法不可行。 为什么呢?因为你每次变更都要重启集群,意味着在集群变 更期间服务不可用,肯定不行啊,太影响用户体验了。想象一下,你正在玩王者荣耀,时不 时弹出一个对话框通知你:系统升级,游戏暂停 3 分钟。这体验糟糕不糟糕?

既然这种方法影响用户体验,根本行不通,那到底怎样解决成员变更的问题呢?最常用的方 法就是单节点变更。

如何通过单节点变更解决成员变更的问题?

单节点变更,就是通过一次变更一个节点实现成员变更。 如果需要变更多个节点,那你需要 执行多次单节点变更。比如将 3 节点集群扩容为 5 节点集群,这时你需要执行 2 次单节点 变更,先将 3 节点集群变更为 4 节点集群,然后再将 4 节点集群变更为 5 节点集群,就像 下图的样子。
在这里插入图片描述

现在,让我们回到开篇的思考题,看看如何用单节点变更的方法,解决这个问题。为了演示 方便,我们假设节点 A 是领导者:

在这里插入图片描述

目前的集群配置为[A, B, C],我们先向集群中加入节点 D,这意味着新配置为[A, B, C, D]。 成员变更,是通过这么两步实现的:

第一步,领导者(节点 A)向新节点(节点 D)同步数据;

第二步,领导者(节点 A)将新配置[A, B, C, D]作为一个日志项,复制到新配置中所有 节点(节点 A、B、C、D)上,然后将新配置的日志项提交到本地状态机,完成单节点 变更。

在这里插入图片描述

在变更完成后,现在的集群配置就是[A, B, C, D],我们再向集群中加入节点 E,也就是说, 新配置为[A, B, C, D, E]。成员变更的步骤和上面类似:

第一步,领导者(节点 A)向新节点(节点 E)同步数据;

第二步,领导者(节点 A)将新配置[A, B, C, D, E]作为一个日志项,复制到新配置中的 所有节点(A、B、C、D、E)上,然后再将新配置的日志项提交到本地状态机,完成单 节点变更。

在这里插入图片描述

这样一来,我们就通过一次变更一个节点的方式,完成了成员变更,保证了集群中始终只有 一个领导者,而且集群也在稳定运行,持续提供服务

我想说的是,在正常情况下,不管旧的集群配置是怎么组成的,旧配置的“大多数”和新配 置的“大多数”都会有一个节点是重叠的。 也就是说,不会同时存在旧配置和新配置 2 个“大多数”:

在这里插入图片描述
在这里插入图片描述

从上图中你可以看到,不管集群是偶数节点,还是奇数节点,不管是增加节点,还是移除节 点,新旧配置的“大多数”都会存在重叠(图中的橙色节点)。

需要你注意的是,在分区错误、节点故障等情况下,如果我们并发执行单节点变更,那么就 可能出现一次单节点变更尚未完成,新的单节点变更又在执行,导致集群出现 2 个领导者 的情况。

如果你遇到这种情况,可以在领导者启动时,创建一个 NO_OP 日志项(也就是空日志 项),只有当领导者将 NO_OP 日志项提交后,再执行成员变更请求。这个解决办法,你 记住就可以了,可以自己在试着研究下

猜你喜欢

转载自blog.csdn.net/lin819747263/article/details/106323461