6.1 节点配置变更

流程详解

流程概览

  1. 用户通过 add_peer/remove_peer/change_peer 接口向 Leader 提交配置变更

  2. 若当前 Leader 有配置变更正在进行,则返回 EBUSY;若新老配置相同,则直接返回成功

  3. 进入日志追赶(CaughtUp)阶段:

    • 3.1 将新老配置做 diff 获得新加入的节点列表;若没有新增节点,则直接进入下一阶段

    • 3.2 为每个新节点创建 Replicator,并对所有新节点定期广播心跳

    • 3.3 为每个新节点安装最新的快照,每当一个节点完成快照的安装,则进行步骤 3.4 的判断

    • 3.4 判断该节点日志与当前 Leader 之间的差距:

      • 3.4.1 若小于一定值(默认 1000 条日志),则将其从追赶列表中移除

      • 3.4.2 若追赶列表为空,即所有新节点都追赶上了 Leader 的日志,则进入下一阶段

    • 3.5 向每个新节点同步日志,每当一个节点成功同步一部分日志,重复步骤 3.4

    • 3.6 若追赶阶段任意节点失败,则本次配置变更标记为失败

  4. 进入联合共识(Joint-Consensus)阶段:

    • 4.1 Leader 应用联合配置(即 C{old,new})为当前配置,以该配置视角进行选举和日志复制:

      • 4.1.1 在该阶段,选举需要同时在新老集群达到 Qourum

      • 4.1.2 在该阶段,日志复制需要同时在新老集群达到 Qourum 才能提交

    • 4.2 Leader 将联合配置日志(即 C{old,new})同时复制给新老集群;当然也复制其他日志

    • 4.3 待联合配置日志在新老集群都达到 Qourum,则提交并应用该日志:

      • 4.3.1 调用该日志的 Closure,进入下一阶段

  5. 进入同步新配置(Stable)阶段:

    • 5.1 Leader 应用新配置(即 C{new})为当前配置,以该配置视角进行选举和日志复制:

      • 5.1.1 在该阶段,选举只需在新集群达到 Qourum

      • 5.1.2 在该阶段,日志只需在新集群达到 Qourum 即可提交

      • 5.1.3 在该阶段,日志仍会被复制给老集群,因为老集群节点的 Replicator 还未停止

    • 5.2 Leader 将新配置日志(即 C{new})同时复制给新老集群;当然也复制其他日志

    • 5.3 待新配置日志在新集群中达到 Qourum,则提交并应用该日志:

      • 5.3.1 以新配置作为参数,调用用户状态机的 on_configuration_committed

      • 5.3.2 调用该配置的 Closure,进入下一阶段

  6. 进行清理工作:

    • 6.1 Leader 移除不在集群中的 Replicator,不再向它们发送心跳和日志

    • 6.2 回调接口传入的 Closure

    • 6.3 若当前 Leader 不在新配置中,则 step_down 变为 Follower:

      • 6.3.1 调用用户状态机的 on_leader_stop

      • 6.3.2 向拥有最长日志的节点发送 TimeoutNow 请求,让其立马进行选举

  7. 对于不在集群中的节点:

    • 7.1 若其拥有了新配置日志,则其发现自身不在集群中,则不会发起选举

    • 7.2 若其未拥有新配置日志,则会超时发起选举,但由于日志落后,其不会通过 PreVote

图 6.1 配置变更示例

干扰集群

图 6.2 被移节点干扰集群示例

上图来自 Raft 作者的博士论文 4.2.3 Disruptive servers,描述的是一个被移除的节点干扰集群,造成集群重新选举的场景:

  • 由节点 S1,S2,S3,S4 组成的集群中,S4 为 Leader

  • 用户执行配置变更,调用 remove_peer 将节点 S1 移除集群

  • S4 同步新配置时,已将新配置持久化到本地,但未复制给 S2,S3

  • 由于 S1 不再接收 S4 的心跳,触发其选举,并在 PreVote 阶段获得 S1,S2,S3 的支持

  • S1 增加自身 term 进行 RequestVoteS4 发现其 term 比自己高,则会 step down

<3.2 选举优化>中提到过,这种场景 PreVote 无法阻止,但是可以利用 Check Quorum 解决。但是其实在目前 braft 的实现中,这种场景是不会出现的,因为 Leader 是将新配置(即 C{new})复制到新集群的大多数集群后,才开始移除不在新集群中的 Replicator,并停止广播心跳。

相关接口

阶段一:追赶日志

调用接口

用户调用各接口要求 Leader 进行配置变更。在这些接口中,会生成新配置和老配置,最终都会调用 on_configuration_committed 执行变更:

开始变更

而在 unsafe_register_conf_change 函数中首先做进行一些判断,决定是否要执行变更。若要进行变更,则调用 ConfigurationCtx::start 正式开始变更:

ConfigurationCtx::start 的主要流程详见以下注释:

创建 Replicator

ReplicatorGroup::add_replicator 会为每个节点创建 Replicator,并将其启动,Replicator 负责发送心跳和同步日志。

关于 Replicator 的创建,我们已经在<3.1 选举流程>中详细介绍过了,见创建 Replicator

保存 Closure

在<开始变更>的主干函数中会调用 ReplicatorGroup::wait_caughtup 为每个新增节点保存 OnCaughtUp,其会在安装快照或同步日志后被调用,来判断追赶进度是否可以进入下一阶段。具体流程见以下注释:

安装快照

Replicator 被创建后,其就会调用 _send_entries 开始复制日志。由于新加入的节点需要的日志很大概率已经被快照压缩了,所以需要向其安装快照。安装快照的流程我们已经在<5.2 安装快照>中详细介绍过来,这里只阐述相关流程:

同步日志

如果日志差距仍大于配置值,则先继续调用 _send_entries 同步日志:

判断日志差距

成功安装快照或每次成功同步一批日之后,都会调用 _notify_on_caught_up 判断新加入节点日志与当前 Leader 的差距。若差距小于一定值(默认为 1000),

阶段二:联合共识

进入新阶段

当所有新增节点都追赶上了 Leader 的日志,则调用 next_stage 进入下一阶段,详见以下注释:

开始变更

unsafe_apply_configuration 具体会执行以下这些工作,详见以下注释,其中的每一项工作我们将在之后逐一介绍:

初始化投票规则

应用联合配置

Leader 调用 check_and_set_configuration 将当前配置变为联合配置,即 C{old,new}

复制日志

调用 LogManager::append_entries 追加配置日志(即 C{old,new}) 后,Replicator 就会被唤醒向 Follower 同步日志。

Leader 会将普通日志和配置日志(即 C{old,new})复制给新老集群,每收到一个节点的成功响应,都会调用 BallotBox::commit_at 将对应日志的计算加一,如果其达到了 Quorum 则提交并应用该日志。具体的日志复制流程我们在之前的章节都详细介绍过了,详见<4.1 复制流程>

提交日志

Leader 本地持久化成功或每成功复制日志给一个 Follower,都会调用 BallotBox::commit_at 将对应日志的复制计数加一,如果达到 Quorum,则更新 commitIndex,并将其应用:

granted 中的 _quorum_old_quorum 都在以上的<初始化投票规则>中设为相应的值了:

应用日志

FSMCaller::on_committed 最终会调用 FSMCaller::do_committed 开始应用日志,具体见以下注释:

回调 Closure

当配置日志被应用后,其会调用 ConfigurationChangeDone::Run,而该函数最终会调用 ConfigurationCtx::next_stage 进入下一阶段:

阶段三:同步新配置

进入新阶段

开始变更

应用新配置

复制日志

提交日志

应用日志

回调 Closure

以上这些步骤跟<阶段二:联合共识>的步骤是一样的,你可以从<开始变更>开始重走一遍逻辑。

阶段四:清理工作

进入新阶段

待新配置被提交后,会调用 next_stage 进入清理阶段:

停止 Replicator

回调 Closure

reset 函数会执行一些清理工作以及回调接口的 Closure,详见以下注释:

stop_replicator 函数会清理不在新配置中 Follower 对应的 Replicator,具体流程见以下注释:

step_down

step_down 函数中会向一个日志最长的节点发送一个 TimeoutNow 请求,让其立马进行选举,该优化我们曾在<3.2 选举优化>中提到过。step_down 的具体流程见以下注释:

其他:故障恢复

当执行配置变更的 Leader 中途 Crash 了,新当选的 Leader 继续执行配置变更:

Last updated