更多内容,前往 IT-BLOG

一、Zookeeper概述


Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。Zookeeper从设计模式角度来理解:是一个基于观察者模式【链接】设计的分布式服务管理框架,它负责存储管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在 Zookeeper上注册的那些观察者做出相应的反应。

hive 注册zookeeper zookeeper注册过程_数据

hive 注册zookeeper zookeeper注册过程_客户端_02

zookeeper 执行流程【1】服务端启动时向 ZK注册服务(创建临时节点);

【2】客户端启动服务时,从 ZK中获取当前在线的服务列表,并注册监听(其实就是提供了一个方法,供注册中心回调);

【3】当服务器某个服务宕机后,ZK会收到宕机信息(删除临时节点),重新修改注册的服务列表

【4】ZK将修改后的服务列表采用通知模式,通知监听的所有消费者(观察者对象);

【5】消费者接收到通知后,从 ZK集群中重新获取最新的服务列表进行通信(服务列表会缓存到本地,当zk不可用时,可以继续访问目标服务);【Zookeeper 的角色zk提供了什么,简单的说,zookeeper = 文件系统+通知机制

【1】领导者(leader):负责进行投票的发起和决议,更新系统状态;

【2】学习者(learner):包括跟随者(Follower)和观察者(Observer),Follower用于接受客户端请求并向客户端返回结果,在选主过程中参与投票;

【3】Observer:可以接受客户端连接,将写请求转发给Leader,但 Observer不参加投票过程,只同步 Leader的状态,Observer的目的是为了扩展系统,提高读取速度;

【4】客户端(client):请求发起方;

hive 注册zookeeper zookeeper注册过程_数据_03

hive 注册zookeeper zookeeper注册过程_客户端_02

二、Zookeeper 特点


Zookeeper 集群概念图如下:CAP原理中 ZK保证的是 AP

hive 注册zookeeper zookeeper注册过程_服务器_05

hive 注册zookeeper zookeeper注册过程_客户端_02

【1】如下图所示ZK集群只有一个领导者(Leader),多个跟随者(Follower)组成的集群;

【2】集群中只要有半数以上节点存活,Zookeeper集群就能正常服务;

【3】全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的;

【4】更新请求顺序进行,来自同一个 Client的更新请求按其发送顺序依次执行;

【5】数据更新原子性,一次数据更新要么成功,要么失败;

【6】实时性,在一定时间范围内(数据量非常小,速度非常快),Client 能读到最新数据;

三、数据结构


ZooKeeper 数据模型的结构与 Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每一个ZNode默认能够存储 1MB的数据,每个 ZNode都可以通过其路径唯一标识。

hive 注册zookeeper zookeeper注册过程_数据_07

hive 注册zookeeper zookeeper注册过程_客户端_02

四、应用场景


【提供的服务包括】:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

统一命名服务在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如:IP不容易记住,而域名容易记住。

 

hive 注册zookeeper zookeeper注册过程_数据_09

hive 注册zookeeper zookeeper注册过程_客户端_02

统一配置管理

【1】分布式环境下,配置文件同步非常常见:

    (1)一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群。

    (2)对配置文件修改后,希望能够快速同步到各个节点上。

【2】配置管理可交由 ZooKeeper实现:

    (1)可将配置信息写入ZooKeeper上的一个Znode。

    (2)各个节点监听这个Znode。

    (3)一旦 Znode中的数据被修改,ZooKeeper将通知各个节点

 

hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_11

hive 注册zookeeper zookeeper注册过程_客户端_02

统一集群管理

【1】分布式环境中,实时掌握每个节点的状态是必要的:可根据节点实时状态做出一些调整(分布式锁);

【2】可交由ZooKeeper实现:

    (1)可将节点信息写入 ZooKeeper上的一个 Znode;

    (2)监听这个 Znode可获取它的实时状态变化;

【3】典型应用:HBase中 Master状态监控与选举;

 

hive 注册zookeeper zookeeper注册过程_客户端_13

hive 注册zookeeper zookeeper注册过程_客户端_02

服务器节点动态上下线客户端能实时洞察到服务器上下线的变化,重点

 

hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_15

hive 注册zookeeper zookeeper注册过程_客户端_02

软负载均衡在 Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求;

 

hive 注册zookeeper zookeeper注册过程_服务器_17

hive 注册zookeeper zookeeper注册过程_客户端_02

五、配置参数解读


Zookeeper 安装【链接

1 # 心跳:2000ms = 2s
 2 tickTime=2000
 3 # 启动时 leader 与 foller 通信的次数 10次,时长 10 * 2s = 20s
 4 initLimit=10
 5 # 启动后 leader 与 foller 通信的次数 5次,时长 5 * 2s = 10s
 6 #集群中Leader与Follower之间的最大响应时间单位
 7 #假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
 8 syncLimit=5
 9 # 存储数据路径
10 dataDir=/usr/local/soft/zookeeper/data
11 #存储日志路径
12 dataLogDir=/usr/local/soft/zookeeper/log
13 # 客户端的端口号
14 clientPort=2181
15 # 集群配置,server.*,*表示myid 中配置的数值,2888 Leader与 Follwer通讯端口,3888选举端口
16 server.1=192.168.52.131:2888:3888
17 server.2=192.168.52.129:2888:3888
18 server.3=192.168.52.130:2888:3888

hive 注册zookeeper zookeeper注册过程_客户端_02

六、Zookeeper 内部原理


Leader 选举机制【1】半数机制:集群中半数以上机器存活,集群可用。所以 Zookeeper适合装在奇数台机器上;

【2】Zookeeper虽然在配置文件中并没有指定 Master和 Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的;

【3】整个选举的过程如下:

 

hive 注册zookeeper zookeeper注册过程_客户端_20

hive 注册zookeeper zookeeper注册过程_客户端_02

假设有五台服务器组成的 Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么。

(1)服务器1启动,此时只有它一台服务器启动了,它发出去的信息没有任何响应,所以它的选举状态一直是 LOOKING状态。

(2)服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态。

(3)服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的 Leader。

(4)服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。

(5)服务器5启动,同4一样当小弟。

七、节点类型


Znode 有两种类型:
【1】短暂(ephemeral):客户端和服务器端断开连接后,创建的节点自动删除;
【2】持久(persistent):客户端和服务器端断开连接后,创建的节点不删除;

Znode 有四种形式的目录节点(默认是persistent ):

【1】持久化目录节点(PERSISTENT):客户端与 Zookeeper断开连接后,该节点依旧存在;

【2】持久化顺序编号目录节点PERSISTENT_SEQUENTIAL):客户端与 Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号;

【3】临时目录节点EPHEMERAL):客户端与 Zookeeper断开连接后,该节点被删除。动态上下线功能靠次实现;

【4】临时顺序编号目录节点EPHEMERAL_SEQUENTIAL):客户端与 Zookeeper断开连接后,该节点被删除,只是 Zookeeper给该节点名称进行顺序编号;

 

hive 注册zookeeper zookeeper注册过程_数据_22

hive 注册zookeeper zookeeper注册过程_客户端_02

八、客户端命令行操作


【1】启动客户端: ./zkCli.sh 启动通过 help 可以查看所有的客户端命令;

 

hive 注册zookeeper zookeeper注册过程_客户端_24

hive 注册zookeeper zookeeper注册过程_客户端_02

【2】查看当前 znode中所包含的内容:ls /

 

hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_26

hive 注册zookeeper zookeeper注册过程_客户端_02

【3】查看当前节点数据并能看到更新次数等数据:ls2 /

 

hive 注册zookeeper zookeeper注册过程_客户端_28

hive 注册zookeeper zookeeper注册过程_客户端_02

【4】创建普通节点: create /app1 "hello app1"

 

hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_30

hive 注册zookeeper zookeeper注册过程_客户端_02

【5】获得节点的值:get /app1

 

hive 注册zookeeper zookeeper注册过程_客户端_32

hive 注册zookeeper zookeeper注册过程_客户端_02

【6】创建短暂节点:create -e /app-emphemeral 8888

 

hive 注册zookeeper zookeeper注册过程_客户端_34

hive 注册zookeeper zookeeper注册过程_客户端_02

【7】创建带序号的节点:①、先创建一个普通的根节点app2:create /app2 "app2";②、创建带序号的节点:create -s /app2/aa 888;

 

hive 注册zookeeper zookeeper注册过程_数据_36

hive 注册zookeeper zookeeper注册过程_客户端_02

【8】修改节点数据值:set /app1 999

 

hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_38

hive 注册zookeeper zookeeper注册过程_客户端_02

【9】节点的值变化监听:get /app1 watch,当节点值发生变化时,通知改客户端,监听一次通知一次,第二次不通知;

  步骤一:客户端1对 app1节点进行监听

 

hive 注册zookeeper zookeeper注册过程_服务器_40

hive 注册zookeeper zookeeper注册过程_客户端_02

  步骤二:客户端2对app1节点值进行修改

 

hive 注册zookeeper zookeeper注册过程_客户端_42

hive 注册zookeeper zookeeper注册过程_客户端_02

  步骤三:客户端1app1节点会收到修改通知

 

hive 注册zookeeper zookeeper注册过程_客户端_44

hive 注册zookeeper zookeeper注册过程_客户端_02

【10】节点的子节点变化监听(路径变化):ls /app1 watch
  步骤一:
客户端1对app1路径进行监听

 

hive 注册zookeeper zookeeper注册过程_服务器_46

hive 注册zookeeper zookeeper注册过程_客户端_02

  步骤二:客户端2在/app1节点上创建子节点: create /app1/bb 666

 

hive 注册zookeeper zookeeper注册过程_数据_48

hive 注册zookeeper zookeeper注册过程_客户端_02

  步骤三:客户端1app1节点会收到路径修改通知

 

hive 注册zookeeper zookeeper注册过程_数据_50

hive 注册zookeeper zookeeper注册过程_客户端_02

【11】删除节点:delete /app1/bb

 

hive 注册zookeeper zookeeper注册过程_数据_52

hive 注册zookeeper zookeeper注册过程_客户端_02

【12】递归删除节点:rmr /app2

 

hive 注册zookeeper zookeeper注册过程_数据_54

hive 注册zookeeper zookeeper注册过程_客户端_02

【13】查看节点状态:stat /app1

 

hive 注册zookeeper zookeeper注册过程_数据_56

hive 注册zookeeper zookeeper注册过程_客户端_02

九、stat结构体


【1】czxid:引起这个 znode创建的 zxid,创建节点的事务的zxid。每次修改 ZooKeeper状态都会收到一个 zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是 ZooKeeper中所有修改总的次序。每个修改都有唯一的 zxid,如果 zxid1小于 zxid2,那么 zxid1在 zxid2之前发生;
【2】ctime:znode被创建的毫秒数(从1970年开始);
【3】mZxid:znode最后更新的zxid;
【4】mtime:znode最后修改的毫秒数(从1970年开始);
【5】pZxid:znode最后更新的子节点zxid;
【6】cversion:znode子节点变化号,znode子节点修改次数;
【7】dataversion:znode数据变化号;
【8】aclVersion:znode访问控制列表的变化号;
【9】ephemeralOwner:如果是临时节点,这个是 znode拥有者的session id。如果不是临时节点则是0;
【10】dataLengthznode的数据长度;
【11】numChildren:znode子节点数量;

十、监听器流程


hive 注册zookeeper zookeeper注册过程_hive 注册zookeeper_58

hive 注册zookeeper zookeeper注册过程_客户端_02

监听原理详解【1】首先要有一个main()线程;
【2】在 main线程中创建 Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener);
【3】通过 connect线程将注册的监听事件发送给 Zookeeper;
【4】在 Zookeeper的注册监听器列表中将注册的监听事件添加到列表中;
【5】Zookeeper监听到有数据或路径变化,就会将这个消息发送给 listener线程;
【6】listener 线程内部调用了process()方法;
常见的监听【1】监听节点数据的变化:get path [watch];
【2】监听子节点增减的变化:ls path [watch];

十一、写数据流程


hive 注册zookeeper zookeeper注册过程_数据_60

hive 注册zookeeper zookeeper注册过程_客户端_02

【1】Client 向 ZooKeeper 的 Server1 上写数据,发送一个写请求;

【2】如果 Server1不是Leader,那么 Server1 会把接受到的请求进一步转发给 Leader,因为每个 ZooKeeper的 Server里面有一个是Leader。这个Leader 会将写请求广播给各个Server,比如Server1和Server2,各个Server写成功后就会通知Leader;

【3】当 Leader收到大多数 Server 数据写成功了,那么就说明数据写成功了。如果这里三个节点的话,只要有两个节点数据写成功了,那么就认为数据写成功了。写成功之后,Leader会告诉 Server1数据写成功了;

【4】Server1会进一步通知 Client 数据写成功了,这时就认为整个写操作成功。ZooKeeper 整个写数据流程就是这样的;

十二、API应用


【1】常见的创建节点、获取节点数据、查看节点是否存在、删除节点、监听事件数据修改、监听节点删除事件等常见操作。

1 @Test
 2 public void demo(){
 3     ZkClient zkClient = new ZkClient("192.168.52.130:2181,192.168.52.131:2181,192.168.52.129:2181");
 4     //监听需要实现序列化
 5     zkClient.setZkSerializer( new MyZkSerializer());
 6     //创建节点
 7     zkClient.createPersistent("/DEMO","demo");
 8     //获取子节点
 9     List<String> children = zkClient.getChildren("/");
10     for(String c:children){
11         System.out.println(c);
12     }
13     //判断节点是否存在,存在返回值,不存在返回null
14     zkClient.exists("/DEMO");
15     //添加节点事件
16     IZkDataListener zkDataListener = new IZkDataListener() {
17         // 节点被删除的时候 事件通知
18         @Override
19         public void handleDataDeleted(String path) throws Exception {
20             System.out.println("删除节点操作");
21         }
22         //节点数据发生改变事件
23         @Override
24         public void handleDataChange(String path, Object data) throws Exception {
25             // 唤醒被等待的线程
26             System.out.println("节点数据发生变化"+path);
27         }
28     };
29     // 注册到zkclient进行监听
30     zkClient.subscribeDataChanges("/DEMO", zkDataListener);
31     //修改节点数据
32     zkClient.writeData("/DEMO","zzx");
33         Thread.sleep(1000);
34     //删除节点
35     zkClient.delete("/DEMO");
36     //查看子节点
37     List<String> children1 = zkClient.getChildren("/");
38     for(String c:children1){
39         System.out.println(c);
40     }
41     while(true){}
42 }

hive 注册zookeeper zookeeper注册过程_客户端_02【2】输出结果查看

hive 注册zookeeper zookeeper注册过程_数据_63

hive 注册zookeeper zookeeper注册过程_客户端_02

【3】序列化需要依赖的pom依赖

1 <dependency>
2     <groupId>org.apache.cocoon</groupId>
3     <artifactId>cocoon-serializers-charsets</artifactId>
4     <version>1.0.0</version>
5 </dependency>

hive 注册zookeeper zookeeper注册过程_客户端_02

【4】序列化Demo

1 package com.distributed.zklock.service.impl;
 2 
 3 import org.I0Itec.zkclient.exception.ZkMarshallingError;
 4 import org.I0Itec.zkclient.serialize.ZkSerializer;
 5 import sun.awt.CharsetString;
 6 
 7 import java.nio.charset.Charset;
 8 
 9 /**
10  * @description:
11  * @author: zzx
12  * @createDate: 2020/5/24
13  * @version: 1.0
14  */
15 public class MyZkSerializer implements ZkSerializer {
16     /**
17      * 序列化,将对象转化为字节数组
18      */
19     public byte[] serialize(Object obj) throws ZkMarshallingError {
20         return String.valueOf(obj).getBytes(Charset.defaultCharset());
21     }
22 
23     /**
24      * 反序列化,将字节数组转化为UTF_8字符串
25      */
26     public Object deserialize(byte[] bytes) throws ZkMarshallingError {
27         return new String(bytes, Charset.defaultCharset());
28     }
29 }