文章目录
raft集群变更再学习
最近再次看raft的中文文档的时候,前面都很顺畅,但是到集群变更这块儿感觉理解的还不是很透彻,于是回过头看了一下英文原文,又从网上参考了一些前辈大佬的文章。感觉有点思路了,故在此记录一下。
在原文上说的是,如果直接从Cold 切换到Cnew的话因为不能做到atomic的原子操作,所以可能会出现两个leader在同一个term当中并存的情况。但是对于具体的场景说的不够细致(也有可能是我的思考的还不够)
这里我尝试增强一些解释
- 每个节点node中都会有一个集群的cluster config (为了和论文中保持一致这里也叫C,之前的配置较
C-old
,新的叫C-new
) - 这里我们假设集群的变更是从3个节点(s1,s2,s3)增加到5个(s1,s2,s3,s4,s5), 在开始的时候leader为s1
- 在一个节点要加入cluster的时候,他会先向集群中的leader发起加入集群的请求,这个时候leader会将
C-old
发给对应的节点(这一点在论文中没有进行详述,只是我认为的一个合理方式) - 集群在选举的时候每个candinate是根据自己的cluster config来决定自己是否赢得了majority选票的(认为自己是leader)
- leader在进行log复制的时候也是根据自己的cluster config来决定是否达成了majority以便可以提交
- 我们在这里为了简化问题,假设新加入的节点和当前集群中的节点是状态同步的(日志没有落后于其他节点),后面我们会单独针对这个情况补充。
单阶段过度的风险分析
论文中说,如果直接从C-old
过度到C-new
会存在在集群中同时存在两个leader的风险,下面我们来尝试分析一下这个过程
- s4,s5,请求加入cluster,从leader上面得到了
C-old
- leader 在本地存储
C-new
,然后将这个日志复制到集群中的其他node - leader 在收到majority数量的复制成功应答后将
C-new
commit(这里的majority肯定是按照新的配置s1-s5来算的) - 集群使用新的
C-new
来管理日志提交
假如运行到第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-old
和C-new
上都满足majority才行,在第一个阶段提交后才会再指向一个C-new
的复制和提交
我们再来阐述一下这个过程,首先还是一个没有出错的过程
- s4,s5,请求加入cluster,从leader上面得到了
C-old
- leader在本地存储
C-(old,new)
,然后将这个日志复制到集群中的其他node - leader 在收到[
C-old
majority数量的复制成功] && [C-new
majority数量的复制成功] 应答后将C-(old,new)
commit - leader在完成3之后在本地存储一个新的配置
C-new
,然后将这个日志复制到集群中的node - leader在收到
C-new
majority数量的复制成功应答后将C-new
commit - 集群使用新的
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论文中提到的实际中面临的一些情况
集群变更时可能存在的其他问题
1. 新加入的节点没有日志
第一个问题是,新的服务器可能初始化没有存储任何的日志条目。当这些服务器以这种状态加入到集群中,那么他们需要一段时间来更新追赶,这时还不能提交新的日志条目。为了避免这种可用性的间隔时间,Raft 在配置更新之前使用了一种额外的阶段,在这个阶段,新的服务器以没有投票权身份加入到集群中来(*复制日志给他们,但是不考虑他们是大多数)。一旦新的服务器追赶上了集群中的其他机器,重新配置可以像上面描述的一样处理。
2. 集群*可能需要下线
第二个问题是,集群的*可能不是新配置的一员(因为服务器问题可能需要先将这个leader服务停掉)。在这种情况下,*就会在提交了 C-new
日志之后退位(回到跟随者状态)。这意味着有这样的一段时间,*管理着集群,但是不包括他自己;他复制日志但是不把他自己算作是大多数之一。当 C-new
被提交时,会发生*过渡,因为这时是最早新的配置可以独立工作的时间点(将总是能够在 C-new
配置下选出新的*)。在此之前,可能只能从 C-old
中选出*。
3. 从C-old
中移除的机器可能会干扰集群
第三个问题是,移除后不在 C-new
中的服务器可能会扰乱集群。这些服务器将不会再接收到心跳,所以当选举超时,他们就会进行新的选举过程。他们会发送拥有新的任期号的请求投票 RPCs,这样会导致当前的*回退成跟随者状态。新的*最终会被选出来,但是被移除的服务器将会再次超时,然后这个过程会再次重复,导致整体可用性大幅降低。
为了避免这个问题,当服务器确认当前*存在时,服务器会忽略请求投票 RPCs。特别的,当服务器在当前最小选举超时时间内收到一个请求投票 RPC,他不会更新当前的任期号或者投出选票。这不会影响正常的选举,每个服务器在开始一次选举之前,至少等待一个最小选举超时时间。然而,这有利于避免被移除的服务器扰乱:如果*能够发送心跳给集群,那么他就不会被更大的任期号废黜。