掘金 后端 ( ) • 2024-05-05 17:13

本文涉及到的原理特殊且较深,作者经验与参考资料不足,不一定完全正确,请读者注意。


前文内容回顾

当 Leader接收到消息请求生成 proposal后就挂了,其他 Follower并没有收到此proposal,因此经过恢复模式重新选了Leader后,这条消息应跳过。注意:

  • 新 Leader选举后 其Epoch值会加一。【每参加一次选举,其Epoch都会加一】 当老的 Leader作为 Follower接入新的 Leader后,如果其上有旧epoch的未提交的proposal,这些proposal将不会被新的Leader接受,因为它们属于一个已经过时的选举周期。这样,就可以确保所有节点上的数据是一致的,简化了数据恢复和同步的过程。

    • 但是注意,老的Leader并不是完全不可能不能参加下一轮选举,不是完全不可能不能当选下一个新的Leader,不是一定会跳过原本Follower没来的及收到的proposal。

现在我们畅谈一下上述标注的注意点!


廉颇老矣,尚能饭否?

通过什么样的机制去保证:老的 Leader挂了以后重启,它不会被选举为 Leader?

当老的Leader挂掉后,Zookeeper集群会自动进入到领导者选举过程。这个过程中,节点(servers)会根据几个关键因素来选择新的Leader,确保老的Leader在重启后几乎不会立即被选为Leader的机制主要包括:

  1. Epoch值:每次选举过程开始时,参与选举的节点会增加其Epoch值,这是一个逻辑时钟,表示领导者选举的轮次。新的Leader必须来自具有最高Epoch值的选举轮次。因此,当老的Leader重新启动并尝试加入集群时,它的Epoch值通常情况下会比当前Leader的小,因为在它宕机期间,集群已经进行了新的领导者选举,Epoch值增加了。

    1. 前置知识:Epoch可以看作是参与选举的次数,反映了集群进行领导者选举的轮次。每当发生一次新的选举,参与选举的所有节点(无论是作为候选的Leader还是作为Follower)都会更新自己的Epoch值,将其设置为它们所知的最大Epoch值加一。这意味着,只要节点持续参与到选举中,它们的Epoch值就会保持在整个Zookeeper集群中的最大值,确保了集群中所有节点的Epoch是一致的
  2. ZXID(Zookeeper Transaction ID) :Zookeeper中每个事务都有一个全局唯一的ZXID,ZXID不仅用于标识事务,也用于在Leader选举中比较节点的数据新旧。新Leader的选举不仅考虑Epoch值,还可能考虑ZXID。因为新的Leader应该是拥有最新数据(即最高ZXID)的节点。

  3. 选票(Votes) :在选举过程中,每个节点都会根据当前已知的最高Epoch和最高ZXID投票。由于重新启动的老Leader的数据可能不是最新的(它的Epoch和ZXID可能都不是最高的),因此它不太可能收到大多数节点的选票【数据也是一个很大的保证,正常情况下,宕机的Leader不可能重启的速度快到最近的一个新事务都没来的及被处理】

特殊情况又是什么呢?

可以想到下面的特殊情况:当一个Leader宕机并重新启动时,如果它及时重新加入集群并参与到了新一轮的选举中,它会将自己的Epoch更新到与其他最新节点一致的值,即当前选举轮次的值。这样,如果它的数据(根据ZXID来判断)是最新的,并且能够获得大多数节点的支持,它仍然有可能被重新选举为Leader

同时考虑:即使原Leader重新启动后其myid相对较小,这也并不直接阻止它再次成为Leader。关键在于该节点是否能够基于其数据状态(主要是ZXID和Epoch)获得集群中大多数节点的支持。如果一个节点(无论其myid大小)在新一轮的选举中展示出它有最新的数据,并且能够获得足够的投票支持,它就有可能被选举为新的Leader。

最终的结论归结如下:原本宕机的Leader并非绝对不能被重新选举为Leader,但由于Zab协议中的机制,其被再次选为Leader的概率实际上是非常小的。


Epoch没有人管管吗?

理论上Epoch值会随着Zookeeper集群进行的选举次数而递增。由于Epoch是一个32位的数值,这意味着它的最大值可以达到2^32−1,也就是4,294,967,295。考虑到Zookeeper集群在正常运行条件下不会频繁进行领导者选举,这个数值为Epoch提供了一个极大的增长空间。

尽管从理论上讲,如果Zookeeper集群运行时间足够长,且经历了大量的领导者选举,Epoch值最终会达到其上限。然而,达到这个上限需要极其长的时间和非常频繁的领导者更换,这在实际运营的分布式系统中是极不常见的。即使在极端情况下达到上限,Zookeeper的设计者和社区可能会考虑实施相应的机制来处理这种情况,比如通过重置或调整Epoch值的策略来避免潜在的问题。

此外,即使理论上存在达到上限的可能性,实际上Zookeeper及其在分布式系统中的使用方式设计得足够健壮,以确保这种极端情况不会影响到其正常的运行和数据一致性的保障。在实际部署和运行中,系统管理员和开发者更可能会因为硬件更新、软件升级或其他运维活动而重启或替换Zookeeper集群的节点,这样也间接避免了Epoch值达到上限的情况。


未提交的proposals所在的老 Leader如果及时参与选举,会不会因为不是旧Epoch而不被新Leader管呢?

当原Leader宕机后迅速恢复,并且成功参与了新一轮的选举但成为了Follower,其未提交的proposals如果与新的Epoch一致,这通常意味着这些proposals是在宕机前不久产生的,但还没有得到集群大多数节点的确认。

新Leader的数据同步过程

在新Leader选举并确定后,即使是原Leader现在作为Follower加入,新Leader也都会启动一次数据同步过程,这个过程包括:

  1. 确定最新的事务状态:新Leader会确定一个全集群共识的最新事务状态,这通常是基于ZXID来决定的,包括Epoch和事务序号。新Leader会从所有Follower(包括原Leader)中收集状态,确定一个大家都认可的最新事务点
  2. 数据同步:新Leader会向所有Follower发送从上述确定的最新事务点开始到当前最新状态的所有proposals,确保所有节点的数据都是一致的。

处理原Leader的未提交proposals

对于原Leader的未提交proposals,如果它们与当前Epoch一致,但是没有得到足够多的Follower确认(也就是没有成为集群的共识) ,那么在新Leader的数据同步过程中,这些proposals有两种可能的处理方式:

  • 如果这些proposals对集群状态没有冲突,即它们是可以接受的变更,新Leader在收集了所有节点的状态后,可能会重新提出这些proposals(现在作为新Leader的proposals),并寻求集群的确认,以此来完成这些变更
  • 如果这些proposals与新Leader已确认的事务状态有冲突,或者新Leader认为这些变更不再适合当前的集群状态,这些proposals将不会被采纳,原Leader必须放弃这些未提交的proposals,并接受来自新Leader的数据状态。

这个机制确保了,即便是原Leader带着与当前Epoch一致的未提交proposals重新加入集群,Zookeeper仍然能通过新Leader的领导和数据同步过程保持集群数据的一致性和完整性。这种方式既考虑了数据的最新性,也保证了集群状态的正确性和一致性。


老 Leader要是当选新 Leader,那么它该如何处理未提交的proposals?

如果原Leader在宕机后迅速恢复,并且在新一轮的选举中成功地再次被选为Leader,这时它携带的未提交的proposals将会被特别处理。在这种情况下,处理原Leader的未提交proposals的机制如下:

验证并尝试提交未提交的proposals

  1. 检查未提交proposals的状态:作为新任Leader,该节点首先需要确定哪些未提交的proposals是在宕机前由它提出的,并且还没有被大多数节点接受。这需要通过检查这些proposals的状态和集群中其他节点的日志来完成。
  2. 决定是否继续推进这些proposals:新Leader需要评估这些未提交的proposals是否仍然有效和适合当前的集群状态。这涉及到比对这些proposals的内容与集群当前状态的兼容性。

提交过程

如果这些未提交的proposals仍然被认为是有效和适当的,新任Leader会采取以下步骤来处理这些proposals:

  • 重新发起这些proposals的提交过程:新任Leader会将这些未提交的proposals重新提出给集群中的其他节点(Follower),请求它们的确认和提交。这一步是必要的,因为在Zookeeper的设计中,一个事务只有在得到集群中大多数节点的确认后才被认为是提交的。
  • 等待集群的确认:新任Leader会等待直到这些proposals被集群中的大多数节点接受和确认。一旦这些proposals获得了足够的确认,它们就被认为是提交的,并且新任Leader会将这些变更正式应用到集群状态中。

数据同步和一致性保证

在整个过程中,新任Leader还需要确保集群中所有节点的数据保持一致性。这可能涉及到在处理未提交proposals的同时,也对集群中的其他节点进行数据同步,确保所有节点都是基于最新和一致的集群状态操作。

如果原Leader成功地再次成为Leader,并且携带了未提交的proposals,它有机会重新推进这些proposals的提交过程。通过重新提交这些proposals并确保它们得到集群中大多数节点的确认,新任Leader可以保证这些变更最终被正确地应用到集群状态中,同时也保证了集群数据的一致性和完整性。