ZooKeeper分布式协调服务组件 05

在这里插入图片描述

一、 Zookeeper概述

1、 概述

Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
在学习zookeeper之前,我们可以想下微信公众号推送文章,微信服务器有两个功能。当公众号作者写文章后,点击发布,这个时候微信服务器做了两件事情,第一件事:将作者的文章保存下来,即实现了文件系统的功能,保存文件。第二件事:给每个订阅了该公众号的微信用户发送通知,而不给那些未订阅的用户推送,即实现了通知功能。
而zookeeper也有这两大功能,即Zookeeper=文件系统(可以在zk上存储数据)+通知机制。我们也可以在zookeeper上存储数据,也可以连接到zookeeper服务器事先订阅的某个数据,然后zookeeper发现数据变化后,会通知客户端。

2、 特点(选举机制)

1)Zookeeper:一个领导者(leader),多个跟随者(follower)组成的集群。
2)Leader负责进行投票的发起和决议,更新系统状态
3)Follower用于接收客户请求并向客户端返回结果,在选举Leader过程中参与投票
4)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所以一般设置奇数台服务器。
5)全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的。
6)更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行。
7)数据更新原子性,一次数据更新要么成功,要么失败。
8)实时性,在一定时间范围内,client能读到最新数据。
在这里插入图片描述全新集群,选举机制其实就是集群内部进行投票,只要选出了leader,后期即使加入新的服务器也不会重新选举。

3、 数据结构

ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个Znode,zookeeper把数据存储在节点上。
很显然zookeeper集群自身维护了一套数据结构。这个存储结构是一个树形结构,其上的每一个节点,我们称之为"znode",每一个znode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识,如图1所示
在这里插入图片描述

4、 下载地址

1.官网首页:
https://zookeeper.apache.org/
2.下载截图,如图5,6,7所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、角色

在这里插入图片描述

二、 Zookeeper安装

1、 分布式安装部署

1.集群规划
在hadoop003、hadoop004和hadoop005三个节点上部署Zookeeper。
在一个节点上配置好,再拷贝分发到其他机器上。
2.解压安装
1)解压zookeeper安装包到/opt/module/目录下

[root@hadoop003 software]$ tar -zxvf zookeeper-3.4.10.tar.gz -C /opt/module/

(2)在/opt/module/zookeeper-3.4.10/这个目录下创建zkData

mkdir -p zkData

(3)重命名/opt/module/zookeeper-3.4.10/conf这个目录下的zoo_sample.cfg为zoo.cfg

cp zoo_sample.cfg zoo.cfg

3.配置zoo.cfg文件
(1)具体配置

dataDir=/opt/module/zookeeper-3.4.10/zkData

增加如下配置

#######################cluster##########################
server.1=hadoop003:2888:3888
server.2=hadoop004:2888:3888
server.3=hadoop005:2888:3888

(2)配置参数解读
server.A=B:C:D。
A是一个数字,表示这个是第几号服务器;
B是这个服务器的ip地址;
C是这个服务器与集群中的Leader服务器交换信息的端口;
D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server。
4.集群操作
(1)在/opt/module/zookeeper-3.4.10/zkData目录下创建一个myid的文件

touch myid

添加myid文件,注意一定要在linux里面创建,在notepad++里面很可能乱码
(2)编辑myid文件

vim myid

在文件中添加与server对应的编号:如1
(3)拷贝配置好的zookeeper分发到其他机器上

scp -r zookeeper-3.4.10/ hadoop004:/opt/module
scp -r zookeeper-3.4.10/ hadoop005:/opt/module
并分别修改myid文件中内容为2、3

(4)分别启动zookeeper

[root@hadoop003 zookeeper-3.4.10]# bin/zkServer.sh start
[root@hadoop004 zookeeper-3.4.10]# bin/zkServer.sh start
[root@hadoop005 zookeeper-3.4.10]# bin/zkServer.sh start

(5)查看状态

jps
[root@hadoop003 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower
[root@hadoop004 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: leader
[root@hadoop005 zookeeper-3.4.5]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower

2、 配置参数解读

解读zoo.cfg 文件中参数含义

  • 1.tickTime:通信心跳数,Zookeeper服务器心跳时间,单位毫秒
    Zookeeper使用的基本时间,服务器之间维持心跳的时间间隔,也就是每隔tickTime时间就会发送一个心跳,时间单位为毫秒。
  • 2.initLimit:LF(leader、follower)初始通信时限
    集群中的follower跟随者服务器(F)与leader领导者服务器(L)之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
  • 3.syncLimit:LF同步通信时限
    集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,
    Leader认为Follwer死掉,从服务器列表中删除Follwer。
    在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。
    如果L发出心跳包在syncLimit之后,还没有从F那收到响应,那么就认为这个F已经不在线了。
  • 4.dataDir:数据文件目录+数据持久化路径
    保存内存数据库快照信息的位置,如果没有其他说明,更新的事务日志也保存到数据库。
  • 5.clientPort:客户端连接端口
    监听客户端连接的端口

三、 Zookeeper内部原理

1、 节点类型

  • 1.Znode有两种类型
    短暂(ephemeral):临时的、客户端和服务器端断开连接后,创建的节点自己删除
    持久(persistent):永久的,客户端和服务器端断开连接后,创建的节点不删除
  • 2.Znode有四种形式的目录节点(默认是persistent )
    (1)持久化目录节点(PERSISTENT)
    客户端与zookeeper断开连接后,该节点依旧存在
    (2)持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
    客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
    (3)临时目录节点(EPHEMERAL)
    客户端与zookeeper断开连接后,该节点被删除
    (4)临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
    客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号,如图9所示
    在这里插入图片描述
  • 3.创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
  • 4.在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序

2、 stat结构体

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

3、 监听器原理

在这里插入图片描述

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

4、 写数据流程(client向zookeeper写入数据)

在这里插入图片描述
ZooKeeper 的写数据流程主要分为以下几步,如图11所示:
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 整个写数据流程就是这样的。

四、 Zookeeper实战

1、 客户端命令行操作

在这里插入图片描述
1.启动客户端

[root@hadoop003 zookeeper-3.4.10]$ bin/zkCli.sh

2.显示所有操作命令

[zk: localhost:2181(CONNECTED) 1] help

3.查看当前znode中所包含的内容

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

4.查看当前节点数据并能看到更新次数等数据

[zk: localhost:2181(CONNECTED) 1] ls2 /
[zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1

5.创建普通节点

[zk: localhost:2181(CONNECTED) 2] create /app1 "hello app1"
Created /app1
[zk: localhost:2181(CONNECTED) 4] create /app1/server101 "192.168.1.101"
Created /app1/server101

6.获得节点的值

[zk: localhost:2181(CONNECTED) 6] get /app1
hello app1
cZxid = 0x20000000a
ctime = Mon Jul 17 16:08:35 CST 2017
mZxid = 0x20000000a
mtime = Mon Jul 17 16:08:35 CST 2017
pZxid = 0x20000000b
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 1
[zk: localhost:2181(CONNECTED) 8] get /app1/server101
192.168.1.101
cZxid = 0x20000000b
ctime = Mon Jul 17 16:11:04 CST 2017
mZxid = 0x20000000b
mtime = Mon Jul 17 16:11:04 CST 2017
pZxid = 0x20000000b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 13
numChildren = 0

7.创建短暂节点

[zk: localhost:2181(CONNECTED) 9] create -e /app-emphemeral 8888

(1)在当前客户端是能查看到的

[zk: localhost:2181(CONNECTED) 10] ls /
[app1, app-emphemeral, zookeeper]

(2)退出当前客户端然后再重启客户端

[zk: localhost:2181(CONNECTED) 12] quit
[bigdata@hadoop104 zookeeper-3.4.10]$ bin/zkCli.sh

(3)再次查看根目录下短暂节点已经删除

[zk: localhost:2181(CONNECTED) 0] ls /
[app1, zookeeper]

8.创建带序号的节点
(1)先创建一个普通的根节点app2

[zk: localhost:2181(CONNECTED) 11] create /app2 "app2"

(2)创建带序号的节点

[zk: localhost:2181(CONNECTED) 13] create -s /app2/aa 888
Created /app2/aa0000000000
[zk: localhost:2181(CONNECTED) 14] create -s /app2/bb 888
Created /app2/bb0000000001
[zk: localhost:2181(CONNECTED) 15] create -s /app2/cc 888
Created /app2/cc0000000002
如果原节点下有1个节点,则再排序时从1开始,以此类推。
[zk: localhost:2181(CONNECTED) 16] create -s /app1/aa 888
Created /app1/aa0000000001

9.修改节点数据值

[zk: localhost:2181(CONNECTED) 2] set /app1 999

10.节点的值变化监听
(1)在hadoop005主机上注册监听/app1节点数据变化

[zk: localhost:2181(CONNECTED) 26] get /app1 watch

(2)在hadoop004主机上修改/app1节点的数据

[zk: localhost:2181(CONNECTED) 5] set /app1  777

(3)观察hadoop005主机收到数据变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/app1

11.节点的子节点变化监听(路径变化)
(1)在hadoop005主机上注册监听/app1节点的子节点变化

[zk: localhost:2181(CONNECTED) 1] ls /app1 watch
[aa0000000001, server101]

(2)在hadoop004主机/app1节点上创建子节点

[zk: localhost:2181(CONNECTED) 6] create /app1/bb 666
Created /app1/bb

(3)观察hadoop005主机收到子节点变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/app1

12.删除节点

[zk: localhost:2181(CONNECTED) 4] delete /app1/bb

13.递归删除节点

[zk: localhost:2181(CONNECTED) 7] rmr /app2

14.查看节点状态

[zk: localhost:2181(CONNECTED) 12] stat /app1
cZxid = 0x20000000a
ctime = Mon Jul 17 16:08:35 CST 2017
mZxid = 0x200000018
mtime = Mon Jul 17 16:54:38 CST 2017
pZxid = 0x20000001c
cversion = 4
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 2

注意:监听只管用一次,要想再次监听,只能再次执行监控命令。

2、 API应用

1) Idea环境搭建

1.创建一个Maven工程
2.添加pom文件

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.4.10</version>
		</dependency>
	</dependencies>

2) Java客户端操作(不带监听)

public class WithoutWatch {
    ZooKeeper client=null;
    @Before//创建客户端
    public void getClient() throws IOException {
        client = new ZooKeeper("hadoop003:2181,hadoop004:2181,hadoop005:2181", 2000, null);

    }
    @Test//创建节点   报NullPointerExceptions=是因为没带监听,监听设为null
    //参数一:节点名
    //参数二:参数值
    //参数三:访问权限,所有客户端都能访问
    //参数四:永久节点
    public void createNode() throws KeeperException, InterruptedException {
        String node = client.create("/idea", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(node);
    }
    @Test//获取节点
    public void getChildrenNode() throws KeeperException, InterruptedException {
        List<String> children = client.getChildren("/",null,null);
        for(String c:children){
            System.out.println(c);
        }
    }
    @Test//获取节点值
    //参数三:获取该节点最新的数据
    public void getNodeData() throws KeeperException, InterruptedException {
        byte[] data = client.getData("/idea", false, null);
        System.out.println(new String(data));
    }
    @Test//修改节点数据
    //参数三:-1,表示修改最新版本号的数据
    public void modifyNodeData() throws KeeperException, InterruptedException {
        Stat stat = client.setData("/idea", "666".getBytes(), -1);
        if(stat==null){
            System.out.println("修改失败");
        }else{
            System.out.println("修改成功");
        }
    }
    @Test//删除节点
    public void deleteNode() throws KeeperException, InterruptedException {
        client.delete("/idea",-1);
        System.out.println("删除成功");
    }
}

3) Java客户端操作(带监听)

public class WithWatch {
    ZooKeeper client=null;
    @Before//创建客户端
    public void getClient() throws IOException {
        client = new ZooKeeper("hadoop003:2181,hadoop004:2181,hadoop005:2181", 2000, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                System.out.println(watchedEvent.getType()+"-"+watchedEvent.getPath());
                try {
                    client.getChildren("/",true,null);

                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

    }
    @Test//获取节点
    public void getChildrenNode() throws KeeperException, InterruptedException {
//        List<String> children = client.getChildren("/",true,null);
//        for(String c:children){
//            System.out.println(c);
//        }
        Thread.sleep(Long.MAX_VALUE);
    }
}

3、 监听服务器动态上下线案例

1.需求
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。
2.需求分析
1)提供时间查询服务,客户端系统调用该时间查询服务。
2)动态上线,下线该时间查询服务的节点,让客户端实时感知服务器列表变化,查询时候访问最新的机器节点。
在这里插入图片描述

3)具体实现:
先在集群上创建/servers节点

[zk: localhost:2181(CONNECTED) 10] create /servers "servers"
Created /servers
public class server {
    public static void main(String[] args) throws Exception {
        //开启zookeeper客户端
        ZooKeeper client = new ZooKeeper("hadoop003:2181,hadoop004:2181,hadoop005:2181", 2000, null);
        //向zookeeper创建/servers的子节点,//127.0.0.1 6666   创建临时有序节点,可以监控服务器上线下线
        String node = client.create("/servers/server", (args[0] + ":" + args[1]).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("服务器上线了,地址为:"+args[0]+":"+args[1]+"---"+node);
        //开启授时服务,发送时间给客户端
        ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[1]));//端口号
        while (true){
            Socket accept = serverSocket.accept();//程序卡在此处监听,有请求才往下执行
            OutputStream outputStream = accept.getOutputStream();
            outputStream.write(new Date().toString().getBytes());//将时间写回给客户端
            System.out.println("本次服务结束");
        }

    }

}
public class client {
    List<String> children=null;
    ZooKeeper client=null;
    List<String> serverList=null;
    public static void main(String[] args) throws Exception {
        com.bigdata.test.client c= new client();
        //开启zookeeper客户端  开启监听,每当servers下的节点数发生变化,以为着服务器上线下线,需要重新获取节点列表,更新在线的服务器列表
        c.getClient();
        //获取/servers子节点,监听节点
        c.getNodeList();




        //向服务器发送请求,获取时间
        c.getTime();//每隔5秒就发送请求,获取一次时间
    }

    public void getClient() throws Exception {
        client = new ZooKeeper("hadoop003:2181,hadoop004:2181,hadoop005:2181", 2000, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                try {
                    getNodeList();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void getNodeList() throws KeeperException, InterruptedException {
        List<String>idAndPost=new ArrayList<String>();
        children = client.getChildren("/servers", true, null);
        for(String node:children){
            byte[] data = client.getData("/servers/" + node, false, null);
            idAndPost.add(new String(data));
        }
        serverList=idAndPost;//存储每个节点的ip和port,相当于是服务器地址列表
    }
    public void getTime() throws IOException, InterruptedException {
        while (true){
            Random random = new Random();
            int i = random.nextInt(serverList.size());
            //127.0.0.1 6666
            String ipPort = serverList.get(i);
            String ip =ipPort.split(":")[0];
            String port = ipPort.split(":")[1];
            //发送请求
            Socket socket = new Socket(ip,Integer.parseInt(port));
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("hello".getBytes());
            byte[] b=new byte[1024];
            InputStream inputStream = socket.getInputStream();
            inputStream.read(b);
            String time = new String(b);
            System.out.println("访问"+ipPort+"获取到的时间是:"+time);
            outputStream.close();
            inputStream.close();
            socket.close();
            //每个5秒发送一次请求,获取一次时间
            Thread.sleep(5000);
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值