写点什么

【Zookeeper 源码阅读】leader 选举源码分析

发布于: 3 小时前

为了保证 Zookeeper 集群的一致性,ZAB协议支持了崩溃恢复模式。而崩溃恢复最关键的部分就是leader选举,下面,我们将详细分析leader选举过程。

Zookeeper 中文注释源码:github.com/chentianmin…

leader选举原理

leader选举存在与两个阶段中,一个是服务器启动时的leader选举。 一个是运行过程中leader节点宕机导致的leader选举。在开始分析选举的原理之前,先了解几个重要的参数:

  1. 服务器 ID(myid):比如有三台服务器,编号分别是 1,2,3。编号越大在选择算法中的权重越大。

  2. 事务 id(zxid):值越大说明数据越新,在选举算法中的权重也越大。高 32 位为epoch,低 32 位为自增 id。

  3. 逻辑时钟(epoch–logicalclock):或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。

  4. 选举状态LOOKING:竞选状态。FOLLOWING:随从状态,同步 leader 状态,参与投票。OBSERVING:观察状态,同步 leader 状态,不参与投票。LEADING:领导者状态。

服务器启动时的leader选举

每个节点启动的时候状态都是LOOKING,处于观望状态,接下来就开始进 行选主流程。

若进行Leader选举,则至少需要两台机器,这里选取 3 台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下:

  1. 每个Server发出一个投票。由于是初始情况,Server1Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myidZXIDepoch,使用(myid,ZXID,epoch)来表示, 此时Server1的投票为(1,0,0),Server2 的投票为(2,0,0),然后各自将这个投票发给集群中其他机器。

  2. 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自LOOKING状态的服务器。

  3. 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK,PK 规则如下:优先对比 epoch,epoch 大的优先级高。其次对比ZXIDZXID比较大的服务器优先作为Leader。如果ZXID相同,那么就比较 myidmyid 较大的服务器作为Leader服务器。

对于Server1而言,它的投票是(1, 0, 0),接收 Server2 的投票为(2, 0, 0), 首先会比较两者的epochZXID,均为 0,再比较myid,此时Server2的 myid 最大,于是更新自己的投票为(2, 0, 0),然后重新投票。对于 Server2而言,其无须更新自己的投票,只是再次向集群中所有机器 发出上一次投票信息即可。

  1. 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于 Server1Server2而言,都统计出集群中已经有两台机器接受了(2, 0, 0)的投票信息,此时便认为已经选出了Leader

  2. 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader, 就变更为LEADING

运行过程中的leader选举

当集群中的leader服务器出现宕机或者不可用的情况时,那么整个集群将无法对外提供服务,而是进入新一轮的 Leader选举,服务器运行期间的Leader选举和启动时期的Leader选举基本过程是一致的。

  1. 变更状态。Leader 挂后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。

  2. 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1ZXID为 123,Server3ZXID为 122。在第一轮投票中,Server1Server3都会投自己,产生投票(1, 123, 0)(3, 122, 0),然后各自将投票发送给集群中所有机器。接收来自各个服务器的投票,与启动时过程相同。

  3. 处理投票。与启动时过程相同,此时,Server1将会成为Leader

  4. 统计投票。与启动时过程相同。

  5. 改变服务器的状态。与启动时过程相同



leader选举的源码分析

源码分析,最关键的是要找到一个入口,对于zkleader选举,并不由客户端来触发,而是在启动的时候会触发一次选举。因此我们可以直去看启动脚本zkServer.sh 中的运行命令。ZOOMAIN就是QuorumPeerMain。那么我们基于这个入口来看:

QuorumPeerMain.main()


main()方法中,调用了initializeAndRun(args)进行初始化并且运行。

QuorumPeerMain.initializeAndRun()


这里主要是加载zoo.cfg配置文件,根据配置运行。

QuorumPeerMain.runFromConfig()



从名字可以看出来,是基于配置文件来进行启动。所以整个方法都是对参数进行解析和设置,因为这些参数暂时还没用, 所以没必要去看。直接看核心的代码quorumPeer.start(),它启动一个线程,那么从这句代码可以看出 QuorumPeer实际是继承了线程。那么它里面一定有一个run()方法。

QuorumPeer.start()



QuorumPeer.start() 方法,重写了Threadstart()方法。在线程启动之前,会做以下操作:

  1. 通过loadDataBase()恢复快照数据

  2. cnxnFactory.start()启动zkServer,相当于用户可以通过2181这个口进行通信了,这块后续再讲。我们还是以leader选举为主线。

QuorumPeer.startLeaderElection()



leader选举的方法:

  1. 构建当前票据

  2. 获取当前zkServer中的myid对应的 ip 地址

  3. 创建选举算法

quorumPeer. createElectionAlgorithm()



根据对应的标识创建选举算法。

FastLeaderElection



初始化FastLeaderElectionQuorumCnxManager是一个很核心的对象,用来实现领导选举中的网络连接管理功能,这个后面会用到。

FastLeaderElection. starter()


starter()方法里面,设置了一些成员属性,并且构建了两个阻塞队列,分是sendQueuerecvqueue。并且实例化了一个Messager

Messenger



Messenger里面构建了两个线程,一个是WorkerSender,一个WorkerReceiver。 这两个线程是分别用来发送和接收消息的线程。具做什么,暂时先不分析。

小结

分析到这里,先做一个简单的总结,通过一个流程图把前面部分串联起来。



ZkServer 服务启动的逻辑

在讲leader选举的时候,有一个cnxnFactory.start()方法来启动 zk 服务,这块具体做了什么呢?我们来分析看看:

runFromConfig中,有构建了一个ServerCnxnFactory



ServerCnxnFactory.createFactory()



这个方法里面是根据 ZOOKEEPER_SERVER_CNXN_FACTORY来决定创建NIO server还是Netty Server,而默认情况下,应该是创建一个NIOServerCnxnFactory

QuorumPeer.start()



我们再回到QuorumPeer.start()方法中,cnxnFactory.start(),应会调用NIOServerCnxnFactory这个类去启动一个线程。

NIOServerCnxnFactory.start()

这里通过thread.start()启动一个线程,那thread是一个什么对象呢?

NIOServerCnxnFactory.configure()



thread其实构建的是一个zookeeperThread线程,并且线程的参数为this, 表示当前 NIOServerCnxnFactory也是实现了线程的类,那么它必须要重写run()方法。

到此,NIOServer的初始化以及启动过程就完成了。并且对2181的这个 端口进行监听。一旦发现有请求进来,就执行相应的处理即可。这块后续在分析数据同步的时候再做详细了解。

选举流程分析


前面分析这么多,还没有正式分析到leader选举的核心流程,前期准工作做好了以后,接下来就开始正式分析 leader选举的过程。

很明显,super.start()表示当前类QuorumPeer继承了线程,线程必须重写run()方法,所以我们可以在 QuorumPeer中找到一个 run()方法。

QuorumPeer.run()



PeerState有几种状态,分别是:

  1. LOOKING,竞选状态。

  2. FOLLOWING,随从状态,同步leader状态,参与投票。

  3. OBSERVING,观察状态,同步leader状态,不参与投票。

  4. LEADING,领导者状态。

对于选举来说,默认都是LOOKING状态,只有LOOKING状态才会去执行选举算法。每个服务器在启动时都会选择 自己做为领导,然后将投票信息发送出去,循环一直到选举出领导为止。

FastLeaderElection.lookForLeader()

leader选举核心代码。





投票处理的流程图



FastLeaderElection.termPredicate()



QuorumMaj. containsQuorum()



判断当前节点的票数是否是大于一半,默认采用QuorumMaj来实现。

投票的网络通信流程




通信过程源码分析

每个 zk 服务启动后创建 socket 监听



run()方法里面就是创建socket监听。



FastLeaderElection.lookForLeader

这个方法在前面分析过,里面会调用 sendNotifications 来发送投票请求:




FastLeaderElection.sendqueue

sendQueue这个队列的数据,是通过WorkerSender来进行获取并发的。而这个WorkerSender线程,在构建 fastLeaderElection时,会启动。



QuorumCnxManager.toSend



startConnection

SendWorker会监听对应sid的阻塞队列,启动的时候回如果队列为空时会重新发送一次最前最后的消息,以防上一次处理是服务器异常退出,造成上一条消息未处理成功;然后就是不停监听队里,发现有消息时调用send()方法RecvWorker:RecvWorker不停监听socketinputstream,读取消息放 到消息接收队列中,消息放入队列中,qcm的流程就完毕了。

leader 选举完成之后的处理逻辑

通过lookForLeader方法选举完成以后,会设置当前节点的PeerState, 要么为Leading、要么就是 FOLLOWING、或者OBSERVING。到这里,只是表示当前的leader选出来了,但是QuorumPeer.run()方法里面还没执行完,我们再回过头看看后续的处理过程。

QuorumPeer.run()



分别来看看caseFOLLOWINGLEADING,会做什么事情:

follower.followLeader()



makeLeader

初始化一个Leader对象,构建一个LeaderZookeeperServer,用于表leader节点的请求处理服务。

leader.lead()

Leader端, 则通过lead()来处理与Follower的交互。

作者:陈添明

链接:https://juejin.cn/post/6989543523213115406

来源:掘金

用户头像

还未添加个人签名 2020.06.14 加入

领取文中资料加微信:gyhycx7980 备注:InfoQ 即可

评论

发布
暂无评论
【Zookeeper源码阅读】leader选举源码分析