注意:如果想要了解更多大数据和算法相关资料,关注有惊喜哦
![4ab917cca290e53068cf7e9724a4653c.png](https://img-blog.csdnimg.cn/img_convert/4ab917cca290e53068cf7e9724a4653c.png)
上文已经对zookeeper服务端的整体启动流程进行了解读,那么接下来将针对QuorumPeer.start方法内部的具体流程做更一步的细化解读
一、服务启动之-加载快照、事务文件到内存生成DataTree模型
- 应用快照文件QuorumPeer在实例化的时候,会创建ZKDatabase实例,该类有以下几个属性;然后会调用该类的loadDataBase方法
![42ed9f085347686a0d00e0f7f7311b5a.png](https://img-blog.csdnimg.cn/img_convert/42ed9f085347686a0d00e0f7f7311b5a.png)
![5833c300e193e226a12aa9609c474f41.png](https://img-blog.csdnimg.cn/img_convert/5833c300e193e226a12aa9609c474f41.png)
2.获取最近一次提交的事务id
3.从zxid中解析获取epoch任期,以及从文件中解析当前epoch
二、服务启动之- Netty服务启动
服务器从快照中恢复数据构建出DataTree模型后,开始启动serverCnxn,建立socket通道,这里启动主要对应两个实现:NIO服务和Netty服务,具体选择哪一种服务,是在初始化quorumPeer时进行配置的
- 创建ServerCnxnFactory调用ServerCnxnFactory.createFactory()方法实例化一个cnxnFactory,而具体选择哪种服务,已经被封装起来,具体细节可见createFactory方法
1.1 可以看到通过反射的方式生成ServerCnxnFactory,默认使用的是NIO,可以通过zookeeper.serverCnxnFactory参数进行配置,如果选用Netty,则配置成 NettyServerCnxnFactory.class.getName()
1.2 这里通过NettyServerCnxnFactory来看一下内部细节
1.2.1 构造函数中,初始化ServerBootstrap对象,设置TCP参数,设置channelHandler
1.2.2 CnxnChannelHandler继承自ChannelDuplexHandler, 它被标注为@Sharable,还是一个共享的处理器。主要 处理各种IO事件。比如客户端连接、断开连接、可读消息
![df38103607d284044d1bcfbc626e9cb3.png](https://img-blog.csdnimg.cn/img_convert/df38103607d284044d1bcfbc626e9cb3.png)
2.调用ServerCnxnFactory.start方法来启动Netty服务
通过调用quorumPeer.start方法来调用cnxnFactory.start方法来启动Netty服务。
quorumPeer.start方法内部封装了一个startServerCnxnFactory方法,而startServerCnxnFactory方法底层调用了cnxnFactory.start方法,即quorumPeer.start--->startServerCnxnFactory--->cnxnFactory.start
![77e623e7e96caea8d6d83317fadbf3aa.png](https://img-blog.csdnimg.cn/img_convert/77e623e7e96caea8d6d83317fadbf3aa.png)
![dd9074effe7f8f9f227852482988e725.png](https://img-blog.csdnimg.cn/img_convert/dd9074effe7f8f9f227852482988e725.png)
三、服务启动之-Leader选举
1.选举初始化
Leader选举入口: QuorumPeer.startLeaderElection() ,大致流程如下:
- 判断当前服务器状态是否为LOOKING,如果是则创建Vote投票实例,服务刚启动时会把票投给自己
- 创建QuorumCnxManager实例,主要负责集群中各个节点的网络IO
- QuorumCnxManager有一个内部类Listener,会启动一个线程用于监听选举端口并处理连接进来的Socket
- 构建一种选举算法FastLeaderElection,早期Zookeeper实现了四种选举算法,但是后面废弃了三种,最新版本只保留FastLeaderElection这一种选举算法
![faff6615d1f376e062f13107c2574ba3.png](https://img-blog.csdnimg.cn/img_convert/faff6615d1f376e062f13107c2574ba3.png)
2.网络io
![fb35fde624d289ff46c6eb8f7b0b6040.png](https://img-blog.csdnimg.cn/img_convert/fb35fde624d289ff46c6eb8f7b0b6040.png)
Leader选举涉及到两个核心类:QuorumCnxManager和FastLeaderElection
红线之上是 QuorumCnxManager工作区域
红色线之下的是FastLeaderElection工作区域 。
选举算法逻辑被封装在FastLeaderElection类中 ;
而QuorumCnxManager则用于管理维护选举期间的网络IO.大致流程:
1.初始化阶段,会实例化QuorumCnxManager,而且会创建内部listener线程,创建ServerSocket,然后循环等待客户端连接(注意:这里的客户端指的是集群其他节点)
2.当有客户端连接进来后,会把该客户端socket封装成 RecvWorker和SendWorker,它们都是线程,分别负责和该Socket所代表的客户端进行读写;RecvWorker和SendWorker是成对出现的,每对负责维护和集群中的一台服务器进行网络IO通信 (注意:这里为了避免资源浪费,只需建立一条通道,即当接收到到一个请求时会进行比对myid,如果对端myid大于自己,则认为连接有效,也就是myid小的一方作为服务端)
3.FastLeaderElection负责Leader选举核心规则算法实现,注意FastLeaderElection类中也包含了两个内部类WorkerSender和WorkerReceiver,类似于QuorumCnxManager中的SendWorker和RecvWorker,也是用于发送和接收线程
4.FastLeaderElection中进行选举时广播投票信息时,将投票信息写入到对端服务器大致流程如下:
4.1.将数据封装成ToSend格式放入到sendqueue;
4.2.WorkerSender线程会一直轮询提取sendqueue中的数据,当提取到ToSend数据后,会获取到集群中所有参与Leader选举节点(除Observer节点外的节点)的sid,如果sid即为本机节点,则转成Notification直接放入到recvqueue中,因为本机不再需要走网络IO;否则放入到queueSendMap中,key是要发送给哪个服务器节点的sid,ByteBuffer即为ToSend的内容,queueSendMap维护的着当前节点要发送的网络数据信息,由于发送到同一个sid服务器可能存在多条数据,所以queueSendMap的value是一个queue类型;
4.3.QuorumCnxManager中的SendWorkder线程不停轮询queueSendMap中是否存在自己要发送的数据,每个SendWorkder线程都会绑定一个sid用于标记该SendWorkder线程和哪个对端服务器进行通信,因此,queueSendMap.get(sid)即可获取该线程要发送数据的queue,然后通过queue.poll()即可提取该线程要发送的数据内容;
4.4.然后通过调用SendWorkder内部维护的socket输出流即可将数据写入到对端服务器
5.FastLeaderElection中进行选举时广播投票信息时,从对端服务器读取投票信息的大致流程如下:
5.1.QuorumCnxManager中的RecvWorker线程会一直从Socket的输入流中读取数据,当读取到对端发送过来的数据时,转成Message格式并放入到recvQueue中;
5.2.FastLeaderElection.WorkerReceiver线程会轮询方式从recvQueue提取数据并转成Notification格式放入到recvqueue中;
5.3.FastLeaderElection从recvqueue提取所有的投票信息进行比较 最终选出一个Leader
![951747d068b0dc585eaf6bd5c621261f.png](https://img-blog.csdnimg.cn/img_convert/951747d068b0dc585eaf6bd5c621261f.png)
![77bea1e5cb47eeb26c815fdb0994885d.png](https://img-blog.csdnimg.cn/img_convert/77bea1e5cb47eeb26c815fdb0994885d.png)
![261e902e0119625560fcf41b00fa5cfb.png](https://img-blog.csdnimg.cn/img_convert/261e902e0119625560fcf41b00fa5cfb.png)
3.Leader选举规则
Leader选举策略使用的是FastLeaderElection ,当检测到serverState状态为LOOKING时进入到LOOKING分支中调用lookForLeader方法开始选举,具体流程如下:
3.1.调用 FastLeaderElection.lookForLeader方法 ,开始选举
3.2.更新自己期望投票信息,即自己期望选哪个服务器作为Leader(用sid代替期望服务器节点)以及该服务器zxid、epoch等信息,第一次投票默认都是投自己当选Leader,然后调用sendNotifications方法广播该投票到集群中所有可以参与投票服务器 (注意:调用 updateProposal 方法来更新投票信息,有三个参数: a.期望投票给哪个服务器(sid)、b.该服务器的zxid、c.该服务器的epoch )
3.3.然后等待其他服务器发送给自己投票信息
3.4.将接收到的投票state进行判断确定接下来执行哪个逻辑
3.4.1 当接收到的状态为LOOKING时,接下来首先比较epoch,如果接收到的epoch比自己的要大,此时要清空之前获取的所有投票, 因为之前获取的投票轮次落后于当前则代表之前的投票已经无效了 ,然后开始根据epoch,zxid,sid进行PK,最后将pk结果同步出去;
当接收到的epoch小于自己时,则直接省略;
当接收到的epoch和自己的一致时,则将投票放入到投票列表集合中,然后进行PK,看是否能够选举出Leader,如果能选举出,则选举结束,并更改自身状态,否则需要继续接收投票;
3.4.2 当接收到的状态为OBSERING时,则忽略投票信息;因为OBSERING不参与投票
3.4.3 当接收到的状态为LEADING时,这时只需要验证是否有效,如果有效则选举结束,否则继续接收投票信息
![c045bb0f5cb686f706ed33349e1be60f.png](https://img-blog.csdnimg.cn/img_convert/c045bb0f5cb686f706ed33349e1be60f.png)
![4f7e2e9518b00a90999c12b24d35419c.png](https://img-blog.csdnimg.cn/img_convert/4f7e2e9518b00a90999c12b24d35419c.png)
![4182bbae27e42e83a006dd1026751324.png](https://img-blog.csdnimg.cn/img_convert/4182bbae27e42e83a006dd1026751324.png)
以上就是对QuorumPeer.start方法入口的主要流程进行了解读,那么当Leader选举出来之后,此时还不能对外提供服务,还需要进行最后一步,即数据同步,保证集群数据一致的情况下,这时才能对外提供服务。那么数据同步就要涉及到Leader端、Follower端和Observer端之间的处理逻辑,该部分内容将在下一篇中详解解读。