raft中集群成员变更

文章目录

raft集群变更再学习

最近再次看raft的中文文档的时候,前面都很顺畅,但是到集群变更这块儿感觉理解的还不是很透彻,于是回过头看了一下英文原文,又从网上参考了一些前辈大佬的文章。感觉有点思路了,故在此记录一下。

在原文上说的是,如果直接从Cold 切换到Cnew的话因为不能做到atomic的原子操作,所以可能会出现两个leader在同一个term当中并存的情况。但是对于具体的场景说的不够细致(也有可能是我的思考的还不够)

这里我尝试增强一些解释

  1. 每个节点node中都会有一个集群的cluster config (为了和论文中保持一致这里也叫C,之前的配置较C-old,新的叫C-new)
  2. 这里我们假设集群的变更是从3个节点(s1,s2,s3)增加到5个(s1,s2,s3,s4,s5), 在开始的时候leader为s1
  3. 在一个节点要加入cluster的时候,他会先向集群中的leader发起加入集群的请求,这个时候leader会将C-old发给对应的节点(这一点在论文中没有进行详述,只是我认为的一个合理方式)
  4. 集群在选举的时候每个candinate是根据自己的cluster config来决定自己是否赢得了majority选票的(认为自己是leader)
  5. leader在进行log复制的时候也是根据自己的cluster config来决定是否达成了majority以便可以提交
  6. 我们在这里为了简化问题,假设新加入的节点和当前集群中的节点是状态同步的(日志没有落后于其他节点),后面我们会单独针对这个情况补充。

单阶段过度的风险分析

论文中说,如果直接从C-old过度到C-new会存在在集群中同时存在两个leader的风险,下面我们来尝试分析一下这个过程

  1. s4,s5,请求加入cluster,从leader上面得到了C-old
  2. leader 在本地存储C-new,然后将这个日志复制到集群中的其他node
  3. leader 在收到majority数量的复制成功应答后将C-newcommit(这里的majority肯定是按照新的配置s1-s5来算的)
  4. 集群使用新的C-new来管理日志提交

raft中集群成员变更

假如运行到第3步,有s1,s4,s5都是C-new, s2,s3为C-old,这个时候s1突然和其他节点发生了短时间的网络分区,导致集群进行了重新选举,
这个时候s2,s3可以通过选举得到一个leader,假设为s2, s1,s4,s5可以通过选举得到一个leader,假如为s4, 那么在当前集群中就会同时存在两个leader,而且term是一致的,可能会引起数据混乱。这种应该就是论文中说的从Cold 切换到Cnew的话因为不能做到atomic的原子操作而导致的问题吧。

两阶段过度的完整性分析

那么raft如何避免这个问题呢,他使用了被称为共同一致的两阶段提交方法,也就是通过两次日志的复制来实现从C-old变成新的C-new,英文称之为joint consensus
这个过程的第一阶段复制的cluster config是C-(old,new), 这个时候要求使用cluster config的时候必须在C-oldC-new上都满足majority才行,在第一个阶段提交后才会再指向一个C-new的复制和提交

我们再来阐述一下这个过程,首先还是一个没有出错的过程

  1. s4,s5,请求加入cluster,从leader上面得到了C-old
  2. leader在本地存储C-(old,new),然后将这个日志复制到集群中的其他node
  3. leader 在收到[C-oldmajority数量的复制成功] && [C-newmajority数量的复制成功] 应答后将C-(old,new)commit
  4. leader在完成3之后在本地存储一个新的配置C-new,然后将这个日志复制到集群中的node
  5. leader在收到C-newmajority数量的复制成功应答后将C-newcommit
  6. 集群使用新的C-new来管理日志提交

我们接下来来重点分析一下在这里如果遭遇了异常应该怎么处理
同样的,假如运行到第3步,有s1,s4,s5都是C-(old,new), s2,s3为C-old,这个时候s1突然和其他节点发生了短时间的网络分区,导致集群进行了重新选举,
这个时候假如s2,s3可以通过选举得到一个leader,假设为s2, 那么s1,s4,s5虽然可以在C-new配置上达成一致,但是无法在C-old上面达成一致(因为s2,s3不可能再给他们投票,每个term只有一次投票机会),所以也就不会出现两个leader的情况。

证明不正确有时候很简单,就像上线的例子,我们只要举一个不安全的反例就行了,但是要想证明正确则可能比较困难,我们需要仔细考量每一个过程可能出现的异常

接着上面的分析,
这个时候如果是s2,s3中的一个成为了leader,那么对应的C-(old,new)会被清除掉(强势leader的规则决定),s4,s5需要重新发起加入请求,等于重新走这个过程
假如s1,s4,s5中的一个称为了leader,那么就会继续完成第3步(让s2,s3中的至少一个复制C-(old,new))接着往下走第4步。
在这整个过程中,无论是哪一个服务器当选leader,C-new都不会单独起作用,这句话非常重要,也就是是他保证了过度的平稳性。

假如运行到第4-5步之间,这个时候无论新旧配置对应的集群都复制了C-(old,new)设置。假如这个时候s1为leader, s1为c-new,s2,s3复制了C-(old,new), s4,s5任然为C-old
假如这个时候leader s1重启,这个时候,因为s4,s5发现cluster config中没有自己,也不会去竞选leader,即使竞选也会因为日志比较落后而失败
如果s2,s3中的任意一个当选leader,那肯定没有问题,因为要满足C-(old,new),这个时候s1肯定没有机会再获得大多数的投票了,
如果s1当选leader,那么s2,s3不管是否投票给s1,都会因为C-(old,new)中的C-new的设置而不会当选leader,所以这个算是是安全的。
raft中集群成员变更

以上就是我对raft的集群配置变更的一些理解。同样,为了这篇文章的完整性,补充一下raft论文中提到的实际中面临的一些情况

集群变更时可能存在的其他问题

1. 新加入的节点没有日志

第一个问题是,新的服务器可能初始化没有存储任何的日志条目。当这些服务器以这种状态加入到集群中,那么他们需要一段时间来更新追赶,这时还不能提交新的日志条目。为了避免这种可用性的间隔时间,Raft 在配置更新之前使用了一种额外的阶段,在这个阶段,新的服务器以没有投票权身份加入到集群中来(*复制日志给他们,但是不考虑他们是大多数)。一旦新的服务器追赶上了集群中的其他机器,重新配置可以像上面描述的一样处理。

2. 集群*可能需要下线

第二个问题是,集群的*可能不是新配置的一员(因为服务器问题可能需要先将这个leader服务停掉)。在这种情况下,*就会在提交了 C-new日志之后退位(回到跟随者状态)。这意味着有这样的一段时间,*管理着集群,但是不包括他自己;他复制日志但是不把他自己算作是大多数之一。当 C-new被提交时,会发生*过渡,因为这时是最早新的配置可以独立工作的时间点(将总是能够在 C-new配置下选出新的*)。在此之前,可能只能从 C-old中选出*。

3. 从C-old中移除的机器可能会干扰集群

第三个问题是,移除后不在 C-new中的服务器可能会扰乱集群。这些服务器将不会再接收到心跳,所以当选举超时,他们就会进行新的选举过程。他们会发送拥有新的任期号的请求投票 RPCs,这样会导致当前的*回退成跟随者状态。新的*最终会被选出来,但是被移除的服务器将会再次超时,然后这个过程会再次重复,导致整体可用性大幅降低。

为了避免这个问题,当服务器确认当前*存在时,服务器会忽略请求投票 RPCs。特别的,当服务器在当前最小选举超时时间内收到一个请求投票 RPC,他不会更新当前的任期号或者投出选票。这不会影响正常的选举,每个服务器在开始一次选举之前,至少等待一个最小选举超时时间。然而,这有利于避免被移除的服务器扰乱:如果*能够发送心跳给集群,那么他就不会被更大的任期号废黜。

参考
raft 中文
raft 英文

上一篇:数据库造数据、建表、复制表、对比表数据


下一篇:ODPS的TRANS_ARRAY应用