首页 > 编程语言 >浅谈Zookeeper集群选举Leader节点源码

浅谈Zookeeper集群选举Leader节点源码

时间:2023-01-18 22:32:54浏览次数:62  
标签:初始化 浅谈 队列 Zookeeper 对应 源码 线程 数据 节点


写在前面:

zookeeper源码比较复杂,本文讲解的重点为各个zookeeper服务节点之间的state选举。至于各个节点之间的数据同步,不在文本的侧重讲解范围内。

在没有对zookeeper组件有一个整体架构认识的基础上,不建议直接死磕细节。本文写作的目的也是基于此,阅读本文,希望读者能够对zookeeper集群架构有一个简单的认识。

本文附有整体架构的执行流程图,请一定要跟着代码多走几遍。本文的二-六节会对该流程中的关键点进行讲解



浅谈Zookeeper集群选举Leader节点源码

  • ​​一、前提共识​​
  • ​​1、了解zookeeper集群搭建​​
  • ​​2、入口为QuorumPeerMain.main方法​​
  • ​​3、整体流程图(重点)​​
  • ​​4、需要有Socket网络编程、多线程相关基础​​
  • ​​二、基础流程​​
  • ​​1、入口main​​
  • ​​2、流程图​​
  • ​​三、多级队列构建流程​​
  • ​​1、多级队列架构讲解​​
  • ​​2、入口startLeaderElection​​
  • ​​3、初始化二级队列​​
  • ​​4、初始化一级队列​​
  • ​​四、LOOKING节点选举流程​​
  • ​​1、节点选举基础流程讲解​​
  • ​​2、LOOKING节点流程​​
  • ​​3、投票PK逻辑源码​​
  • ​​4、其余节点​​
  • ​​五、Zookeeper之间数据同步​​
  • ​​1、初始化LEADING节点​​
  • ​​2、初始化新端口的服务端​​
  • ​​六、LEADING节点挂之后流程​​
  • ​​1、LEADING节点会不断发送PING命令​​
  • ​​2、FOLLOWING节点不断接收命令​​



一、前提共识

1、了解zookeeper集群搭建

不了解如何搭建集群的可以参考网上别人的文章:​​Zookeeper集群搭建及使用​

本文需要了解集群相关的配置为zoo.cfg文件的节点配置信息:

server.1=第一台服务器的主机名:2881:3881
server.2=第二台服务器的主机名:2882:3882
server.3=第三台服务器的主机名:2883:3882



这里针对每一个zookeeper服务,分别含有288x和388x两个端口。第一个端口用来同步节点之间的数据,第二个端口用来进行节点的state选举(对应代码为ServerState枚举类)

在源码中会new很多的Socket通信,一定要区分开不同端口的实例化对象。


2、入口为QuorumPeerMain.main方法

至于为什么直接是这个方法,可以查看对应的脚本文件。大部分使用脚本文件进行执行的,都是同样的方式(如tomcat)。

如果左边的sh文件不方便看,可以看右边的sh文件(毕竟逻辑更简单)



浅谈Zookeeper集群选举Leader节点源码_socket


3、整体流程图(重点)

一定要一定要,跟着流程图多走几遍



浅谈Zookeeper集群选举Leader节点源码_java_02


4、需要有Socket网络编程、多线程相关基础

源代码中出现大量的Socket通信,也包含创建大量的多线程代码。不了解这两部分知识的小伙伴,看起来可能比较吃力



二、基础流程

1、入口main



浅谈Zookeeper集群选举Leader节点源码_网络_03


2、流程图

这部分比较简单,主要完成的几件事情可以总结为:

  1. 解析配置文件的配置信息
  2. 初始化zookeeper用于接收客户端命令(create、delete、get)的服务端,可选择Netty或者NIO方式,并且完成对应的服务端的启动
  3. 设置对应的集群leader选举的选举类型(设置为3,未来会在Switch中用到该值)
  4. 启动一个内嵌的jetty服务器,用来查看未来服务端的信息
  5. 初始化对应的多级队列架构
  6. 进行具体的集群leader节点的选举

浅谈Zookeeper集群选举Leader节点源码_socket_04



三、多级队列构建流程

1、多级队列架构讲解

看不懂这个图的,建议先把源码跟一遍,再回头看

首先看一下多级队列架构图



浅谈Zookeeper集群选举Leader节点源码_socket_05


我们可以将该多级队列可以分为两层:

第一层:应用层,可以理解为我们上层api队列

代表队列:sendqueue、recvqueue

对应线程名称:WorkerSender、WorkerReceiver



第二层:传输层,可以理解为后台传送数据的队列

代表队列:recvQueue、queueSendMap(map的value为传输层队列)、senderWorkerMap(map的value为SendWorker线程)

对应线程名称:SendWorker、RecvWorker



牢记上面这几个队列、map、线程的名字。否则代码你会看不清楚


处理多级队列流程大致可以描述为

1、首先会初始化对应的ServerSocket端,同时进行accept阻塞监听数据;

2、如果处理leader节点对应的端口接收到数据,那么就会处理该请求。刚开始初始化的时候是不会有数据的,毕竟Socket客户端都还没有开始建立,在第10步才建立的;

3、然后会对信息中的sid进行判断,然后剔除掉不需要的socket连接。因为socket通信是全体连接都发一遍,zk的判定规则为只能由sid大的发给sid小的,所以如果小的发送给了大的,就会关闭连接,并且开启一个反向连接;

4、如果是小的发送给大的,此时就会初始化出我们多级队列的第二级队列传输层队列,以及对应的SendWorker线程任务。然后把这个SendWorker任务放入senderWorkerMap中,未来可以直接取出来直接使用;

5、这里的SendWorker任务,就是真实的把数据通过Socket请求把数据发送给远端的逻辑;

6、接下来开始处理第一级队列应用层队列;

7、即初始化sendqueue和recvqueue这两个上层传输队列;

8、然后就是初始化对应的Socket客户端。当然在初始化客户端的前提是,我们有业务数据放到了第一级的队列中,即第7步中的队列中要有数据;

9、上层业务队列有数据以后,会将业务数据封装为一个ByteBuffer类型的requestBuffer对象,然后把数据直接发送给传输层的队列(queueSendMap中对应sid的value对应的队列);

10、输入发送后,初始化对应的Socket客户端连接,这样就能够把数据发送出去,与此同时,第2步的accept也能接收到数据,最终形成两级队列工作工作的闭环。



第10步使用Socket发送的数据,会与第2步的ServerSocket连接打通。一定要明确这是在集群环境下的通信,明白这一步,很重要


与之对应的recvqueue只是逻辑反一下,首先RecvWorker线程执行会获取到数据,发送给一级队列应用层队列recvqueue,一级队列对应的WQorkRecevier线程任务,就会执行对应的逻辑,然后处理数据。


2、入口startLeaderElection



浅谈Zookeeper集群选举Leader节点源码_socket_06



浅谈Zookeeper集群选举Leader节点源码_网络_07


3、初始化二级队列

1)Listener方法内部

  • 绑定服务端信息
  • accept数据
  • 处理数据


浅谈Zookeeper集群选举Leader节点源码_分布式_08


2)根据接收到的信息的sid情况进行不同的逻辑

  • 如果是自己的就发给自己
  • 如果不是自己,并且满足zk的sid由大机器发送给小机器的规则,则开启对应的两个用于处理二级队列(传输层队列)的线程任务


浅谈Zookeeper集群选举Leader节点源码_网络_09


3)以SendWorker线程任务为例

  • queueSendMap中获取对应sid的队列,然后获取应用层队列中的数据,并将其发送给传输层队列
  • 最终调用send方法,完成数据的传输


浅谈Zookeeper集群选举Leader节点源码_zookeeper_10


浅谈Zookeeper集群选举Leader节点源码_zookeeper_11


4、初始化一级队列

1)完成一级队列的初始化



浅谈Zookeeper集群选举Leader节点源码_zookeeper_12

2)完成一级队列对应的线程任务的初始化



浅谈Zookeeper集群选举Leader节点源码_网络_13

3)调用对应线程的run方法,然后调用process方法处理数据



浅谈Zookeeper集群选举Leader节点源码_java_14

4)把数据发送给运输层队列



浅谈Zookeeper集群选举Leader节点源码_socket_15

5)建立socket客户端连接

  • 首先调用内部的connetOne方法
  • 然后把数据封装为一个任务,放进线程池
  • 在线程任务中会初始化对应的Socket客户端连接
  • 封装数据写入到输出流。此时二级队列初始化出来的阻塞的ServerSocket就能够开始向下执行,处理接收到的数据,形成闭环


浅谈Zookeeper集群选举Leader节点源码_java_16

将线程任务放进线程池代码



浅谈Zookeeper集群选举Leader节点源码_分布式_17

该任务会初始化对应的客户端



浅谈Zookeeper集群选举Leader节点源码_java_18

封装对应的输出数据,并完成发送



浅谈Zookeeper集群选举Leader节点源码_分布式_19



四、LOOKING节点选举流程

1、节点选举基础流程讲解



浅谈Zookeeper集群选举Leader节点源码_java_20

流程选举大致流程可以描述为:

1、第一轮选票:myid为1和myid为2的两台机器,分别把自己的选票发送出去(vote形式),两台机器会分别对自己收到的选票和自己的选票进行比较。会根据指定的判断规则进行选择(粗略可以理解为,是周期、zxid和机器id几个要素中大的);

2、myid为1的机器收到(2,0),会和自己的选票(1,0)进行比较,发现还是(2,0)大,所以最终它会把(2,0)返回回去。此轮中,两台机器发送的选票都没有超过半数(一共3台服务,配置文件中能获取);

3、第二轮选票:myid为1和myid为2的两台机器,再次发送自己的选票信息。此时由于myid为1的这台机器发送的就是上一轮接收到的(2,0)选票。那么在第二轮投票时myid为1的机器就会投出(2,0),与此同时,myid为2的机器投出的选票也是(2,0);

4、此轮中投出的选票(2,0)超过了半数(2/3)。最终myid为2的这台机器被选为了leader节点;

5、当myid为3的节点进来的时候,虽然它的(3,0)大于(2,0),但是其周期比较小(参与投票的周期次数),所以myid为2的机器依然还是leader节点。


2、LOOKING节点流程

每一个节点进来以后的第一步逻辑就是这个位置



浅谈Zookeeper集群选举Leader节点源码_分布式_21


3、投票PK逻辑源码

1)首先会把自己的投票信息发送出去,然后再获取自己的recvqueue队列(传输层队列,具体逻辑在多级队列章节进行过讲解)中的投票信息,只要满足条件,就可以不断的去获取队列中的数据



浅谈Zookeeper集群选举Leader节点源码_java_22

2)紧接着对接收到的选票进行逻辑判断。根据其不同的state走不同的逻辑,最终根据判断逻辑再次把消息发送出去



浅谈Zookeeper集群选举Leader节点源码_分布式_23

3)这是很重要的一个判断规则,根据指定的要素进行判定两个选票的规则



浅谈Zookeeper集群选举Leader节点源码_socket_24

4)会调用该方法,最终走到其半数判定的条件。最终返回选择出来的leader节点,即endVote对象,如果没有选择到leader节点,则返回null



浅谈Zookeeper集群选举Leader节点源码_zookeeper_25


浅谈Zookeeper集群选举Leader节点源码_java_26


4、其余节点

LOOKING节点的流程为主要逻辑,在第一轮的while循环过后,就能够明确每种节点对应的类型是什么,第二次while循环的时候,就会走到对应的节点方法中去。详细的执行逻辑可请参考开头的那张流程图



浅谈Zookeeper集群选举Leader节点源码_网络_27



五、Zookeeper之间数据同步

这里就回到了第一节中的,第一个共识。即配置集群的时候是有两个端口,最后面的端口,是用于集群节点的选举,倒数第二个端口则是用于同步Zookeeper节点之间的数据。

既然出现了新的端口通信,那么就会初始化新的服务端

1、初始化LEADING节点



浅谈Zookeeper集群选举Leader节点源码_分布式_28


2、初始化新端口的服务端

这里就会根据新的配置,初始化新的服务端,用来接收zookeeper客户端之间的命令



浅谈Zookeeper集群选举Leader节点源码_网络_29



六、LEADING节点挂之后流程

1、LEADING节点会不断发送PING命令

1)如果是leader节点则会走这段逻辑



浅谈Zookeeper集群选举Leader节点源码_socket_30

2)在一个while死循环内部,会周期性的给learner发送ping数据



浅谈Zookeeper集群选举Leader节点源码_分布式_31

3)封装对应的PING数据包



浅谈Zookeeper集群选举Leader节点源码_socket_32


2、FOLLOWING节点不断接收命令

1)只要机器还运行着,就会轮循该方法,节点是FOLLOWING的机器就会调用follwLeader方法



浅谈Zookeeper集群选举Leader节点源码_分布式_33

2)该方法中会调用readPacket方法,去接收数据。后面的processPacket方法会对接受到的数据进行处理。在这过程中就会出现获取不到数据,然后抛异常。那么就会跳转到上一步,在updateServerState()方法中,就会根据逻辑,把自己的FOLLWING节点设置为LOOKING节点,继而触发后续的再次选举流程



浅谈Zookeeper集群选举Leader节点源码_socket_34


至此Zookeeper中Leader节点选取的流程就告一段落。开头那张整体的流程图,一定要多跟着源码多走几遍。


标签:初始化,浅谈,队列,Zookeeper,对应,源码,线程,数据,节点
From: https://blog.51cto.com/u_15942107/6019543

相关文章

  • 浅谈Redis基本数据类型底层编码(含C源码)
    文章目录​​一、String​​​​1、int​​​​2、embstr​​​​3、raw​​​​4、bitmap​​​​5、hyperloglog​​​​二、List​​​​1、ziplist​​​​2、quicklist......
  • 浅谈如何设计MySQL索引
    文章目录​​一、索引的代价​​​​二、如何设计索引​​​​1、索引列的类型尽量小​​​​2、索引的选择离散性高的​​​​3、只为用于搜索、排序或分组的列创建索引​......
  • STM32 PLC底层源码 FX2N源码 断电保持/Keil源码
    STM32PLC底层源码FX2N源码断电保持/Keil源码三菱指令编码注释较多,适合初学者,发编译环境:KeilMDK4.7以上的版本,CPU需要:STM32F103--RAM内存不小64K,Flash程序空间不小于256......
  • JS 前端大文件上传源码
    ​对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程......
  • JDK 1.8 TreeMap源码分析
    /**   *TreeMap特点:   * 底层:二叉红黑树key输入无序,升序排列,null不可以   * 1.2    */publicclassTreeMap<K,V>   extendsAbstractMap<K......
  • JDK 1.8 Hashtable的源码分析
       /**   *Hashtable特点:   * 与hashTable一样1.1效率低,线程安全,key不为null hashMap1.2 效率高,key为null长度11    */publicclassHashta......
  • JDK 1.8 HashMap的源码分析
       /**   *HashMap的特点:   *1.AbstractMapMap冗余   *2.与hashTable一样1.1效率低,线程安全,key不为null hashMap1.2 效率高,key为null ......
  • JDK 1.8 TreeSet 源码分析
       /**   *TreeSet的特点:无序 唯一需要比较器自定义<>中的内容需要实现comparable的接口推荐外部实现:多态,自定义多种规则   *底层实现逻辑:二叉红黑......
  • JDK 1.8 hashset的源码分析
        /**   *hashset的特点:无序 唯一需要比较器自定义<>中的内容需要实现comparable的接口推荐外部实现:多态,自定义多种规则   *底层实现逻辑:数......
  • JDK1.8 Vector 源码相关分析
       /**   *底层实现逻辑:数组线性表查询效率高,修改效率低    *所有的操作类同ArrayList但是synchronize线程安全    *效率低    */public......