Zookeeper 源码分析--Zookeeper启动流程(一)

Zookeeper 源码分析–Zookeeper启动流程(一)

最近学习了一下Zookeeper,怎么说呢,总起来就学到了两个概念性的名词,当然这两个名词均和一个Zookeeper的协议相关,那就是ZAB协议,来名词呈上来:崩溃恢复和消息广播。但是俗话说万事开头难,所以这次就先聊下Zookeeper是怎么启动的。

Zookeeper启动之预热准备

我们身为Java码农,初始化对象的过程,我们都了解,需要做好多的准备,虽然只是简单的new一下,但是也作了很多准备。话说回来,zookeeper的启动也可以理解为启动一个大的对象的过程,一个运行在服务端的实例“大对象”。所以也需要在创建这个大对象之前做点什么,例如zookeeper的配置等,这是需要在这个时候要做的。

在在大学时候学的hello world吗?是不是放在一个main函数里面?是的,zookeeper关于初始化启动也有一个main,只不过是一个类–QuorumPeerMain类。
在这里插入图片描述

看,这里有个main方法,快抓住他。可以和清晰看到,核心工作集只有一个函数的调用,调用了initializeAndRun方法,正如名字,其实就是初始化和run,那到这个方法看下究竟做了什么?
在这里插入图片描述
可以看到,QuorumPeerConfig类是用来读取配置的,也就是说,当我们启动上述的initializeAndRun方法就会先去读取配置,当然也同时开辟一个新的线程,获取DatadirCleanupManager实例去清理了一些历史文件,以及一些快照等。无论参数个数为多少,最终都是要使用QuorumPeerConfig类的parse方法去解析,那看下QuorumeerConfig#parse方法吧:
在这里插入图片描述
除了做了一些校验外,创建了一个Properties属性,注意,这里的Properties是Java util包下面的,不是zookeeper的,其本质结构为HashTable,其实就是为了应对强一致性的场景,毕竟在我们配置zoo.cfg的时候已经规定了对应的关系。所以可看到,FIleInputStream读取了一个文件,在第一行我们看到了参数名为path,所以也就可以理解,这里是读取的zoo.cfg文件。除了上述过程外,另外一个就是parseProperties方法了,那看下这个方法做了什么?整个方法逻辑太多了,就不全部展示了,但是展示的这些应该可以理解这个方法的作用了。
在这里插入图片描述
这些关键词好像在那里见过,没错就是在zoo.cfg中,可以看到,这个函数其实就是对QuorumPeerConfig类初始化成员变量的过程。到这里我们已经明白是如何将zoo.cfg文件的配置内容读取进来的,也确实体会到我们的配置是从这开始进入到zookeeper的。

回归mian方法,接着往下可以看到,我们的config被使用了:
在这里插入图片描述
但这里也同时有了两个选择,总体上来说是如果配置了zoo.cfg并且配置的服务器数量多于一个就执行runFromConfig,否则就启动单机版的zookeeper,可以看到,Zookeeper调用了其类方法main,并把之前配置文件的path传进去。由于单机版的zookeeper可看作一个通用的服务器,所以流程上也不会涉及到选举,崩溃恢复等集群行为,所以后续的分析则依据集群版本的zookeeper来分析。那我们继续沿着runFromConfig方法走:

首先注册了logbean,用来记录打印日志:
在这里插入图片描述

托管者QuorumPeer创建

接着创建了QuorumPeer对象,为什么要创建这个对象呢,其实这个对象是Zookeeper服务器实例的托管人,从集群角度看,其代表了一台机器,在运行期间不断的检测集群中的服务器的状态,同时,当集群中leader挂掉后,其会发起选举。此对象用来记录之前的各种配置,例如这些操作,一顿get和set,其实本质上就是两个不同的对象中相同成员变量的状态转移,由config转移到QuorumPeer的实例对象:
在这里插入图片描述
但是在上面getQuorumPeer对象的过程中,新建QuorumPeer对象也同时生成了此时的服务的状态,Status:
在这里插入图片描述
意思也就是说,在上面一顿get /set之前,就先完成了服务器状态的设置,由于QuorumPeer类继承了Provider类,这里也就证明其是属于生产端,所以能够把自己(QuorumPeer类的实例对象)用来构造在QuorumStats。同时也可以看到initialize方法也被调用了,其实这里是用初始化状态的:
在这里插入图片描述
这里总体上是初始化了两类服务器,核心点在于是否启用了sasl认证,默认是不开启的,所以这其实和前面runFromConfig方法里面的一顿get set由关系的:
在这里插入图片描述

创建服务器端ServerCnxn服务(守护线程)

可以看到,如果我们配置里面启用了认证,那么就会生成带有sasl认证的server和learner两类服务器。与此同时,也同时建立了服务器端用来和客户端进行通信的的实例,以及最大连接数:
在这里插入图片描述
同样的,服务端的ServerCnxn是基于NIO的还是基于Nettey的,以及客户端最大连接数,以及dataDir以及dataLogDir等都是基于zoo.cfg中的配置来完成的。至此,初始化预热工作已经完成了。
在这里插入图片描述
在上面的代码中可以了解到默认是生成基于Java NIO的servercnxnFactory实例对象。对应的cnxnFactory的configure方法根据配置在zoo.cfg中配置的客户端通信的端口地址,以及可允许链接的客户端最大数量。由下面的代码可知,第一行的方法其实是对开启了sasl认证才有效的。下面的实现中可以看到,新创建了一个ZooKeeperThread,已经注册了新的ServerSocketChannel,然后绑定了对应的地址。最后将创建的ServerSocketChannel进行注册。其实在这里可以看到,此这个servercnxnFactory所在的线程被设置为了守护线程,意在表明其可能是贯穿整个zookeeper生命周期的,所以一般的守护线程是要等到最后才销毁的。
在这里插入图片描述
关于zkDataBase的分析可参考:https://blog.csdn.net/lxxc11/article/details/48806391

配置完成,开始启动

然后在runFromConfig中开始启动了:
在这里插入图片描述
所以,在这里可以了解到,一个作为provider的实例在新开辟的一个线程上启动了。在start方法中可以看到做了4个事情:加载数据库,在新的线程中启动服务器端的ServerCnxnFactory,开始快速选举,是不是熟悉的味道,服务器集群启动完成后进入选举的阶段。
在这里插入图片描述
loadDataBase其实就是从文件获取UPDATING_EPOCH和CURRENT_EPOCH等得到目前有效的事务ID:lastProcessedZxid和当前的epochOfZxid。前面讲到过根据zoo.cfg中配置的logDir和dataDir可以创建FIleSnapLog对象,其为Zookeeper中的数据存储管理器–FileTxnSnaplog类,此类作为最上层的,提供了一系列了操作数据文件的接口,其中包括操作事务日志和操作快照的接口,而创建当前类实例会根据zoo.cfg中的dataDir以及dataLogDir参数进行构建
在这里插入图片描述

如果是默认启动,上面的cnxnFactory.start()方法启动的是NIOServerCnxnFactory的线程。此时NIOServerCnxnFactory类型的线程是仅启动过一次,而且也只有一次。
在这里插入图片描述

选举开始(election)

启动了NIOServerCnxnFactory的守护线程之后,也就有了发送数据集进行通信的信道,则开始选举,调用startLeaderElection:
在这里插入图片描述
由前面loadDataBase方法可得到生成Vote投票所需要的信息,如当前事务ID,当前轮数epoch以及配置的myid,myid为我们在启动zookeeper前设定的。getView方法获取了所有配置的server,然后遍历查询当前的id是否和其中某一个的server的id是否相同,如相同,那将托管者的地址设置为得到的这个已有的服务器的地址,如果没有在zoo.cfg中没有配置使用的选举算法,那么就默认的则直接用基于TCP的选举算法LeaderElection。也就是说当第一次启动后选举leader的时候若不配置electionAlg,则electionType为3 (具体来源于config中的默认配置为3),那Zookeeper中实现了哪些选举方法呢?
在这里插入图片描述
有兴趣可以沿着其他的分支,看下其他的算法实现,在这里的话我们还是沿着我们的默认集群配置中的选举算法,也就是case 3的分支继续分析:首先,调用createCnxnManager方法创建了QuorumCnxManager实例对象,这里使用的构建QuorumCnxManager的参数全部来自于当前QuorumPeer类,本质上还是zoo.cfg和config中的内容。同时启动了一个QuorumCnxManager的Listener,用来监听之前创建的QuorumPeer中保存的当前的地址和端口,所以可以看到while循环中使用的是socket的客户端来接受绑定端口的传输过来的信息。
在这里插入图片描述
而setSockOpts则设置TCP链接是否是长链接以及其超时时间。
在这里插入图片描述
当socket中来了信息传输过来时,先判断是不是sasl认证的,如果是,则执行receiveConnectionAsync方法,此方法通过开辟线程池connectionExecutor中新的线程来处理。
在这里插入图片描述
那新生成的这个对象其类的定义仍然在Manager中,所以我们找到其定义看下到底做了什么?
在这里插入图片描述
可以看到传递了sock并重写了ZookeeperThread方法的run方法,但这里是利用线程池来处理的,但是本质上无论是不使用sasl的else分支还是上述分析的if分支,其最终都是通过receiveConnnection方法完成处理的,核心方法为handleConnection方法。说到这里,现在假设这个场景:集群中存在A、B两个节点:

  1. 当A节点连接B节点时,在B节点上会维护一对RecvWorker和SendWorker用于B节点和A节点进行通信;
  2. 同理,如果B节点连接A节点,在A节点上会维护一对RecvWorker和SendWorker用于A节点和B节点进行通信;
  3. A和B之间创建了两条通道,实际上A和B间通信只需要一条通道即可,为避免浪费资源,Zookeeper采用如下原则:myid小的一方作为服务端,否则连接无效会被关闭;
  4. 比如A的myid是1,B的myid是2,如果A去连接B,B收到连接请求后B发现对端myid小于自己,判定该连接无效,会关闭该连接;如果是B连接A,A收到连接请求后发现对端myid大于自己,则认为该连接有效,并会为该连接创建一对RecvWorker和SendWorker线程并启动
    在这里插入图片描述
    所以我们对QuorumBeer所使用的接口的监听也已经启动了,接下来就是leader选举了:
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值