Zookeeper基本使用

Posted 拐柒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zookeeper基本使用相关的知识,希望对你有一定的参考价值。

zk基本使用

数据模型

ZooKeeper数据模型Znode

在ZooKeeper中,数据信息被保存在⼀个个数据节点上,这些节点被称为znode。ZNode 是Zookeeper 中最⼩数据单位,在 ZNode 下⾯⼜可以再挂 ZNode,这样⼀层层下去就形成了⼀个层次化命名空间 ZNode 树,我们称为 ZNode Tree,它采⽤了类似⽂件系统的层级树状结构进⾏管理。

在 Zookeeper 中,每⼀个数据节点都是⼀个 ZNode,上图根⽬录下有两个节点,分别是:app1 和app2,其中 app1 下⾯⼜有三个⼦节点,所有ZNode按层次化进⾏组织,形成这么⼀颗树,ZNode的节点路径标识⽅式和Unix⽂件系统路径⾮常相似,都是由⼀系列使⽤斜杠(/)进⾏分割的路径表示,开发⼈员可以向这个节点写⼊数据,也可以在这个节点下⾯创建⼦节点。

节点类型

Zookeeper 节点类型可以分为三⼤类:

  • 持久性节点(Persistent)
  • 临时性节点(Ephemeral)
  • 顺序性节点(Sequential)
    在开发中在创建节点的时候通过组合可以⽣成以下四种节点类型:持久节点、持久顺序节点、临时节 点、临时顺序节点。不同类型的节点则会有不同的⽣命周期
    持久节点:节点被创建后会⼀直存在服务器,直到删除操作主动清除
    持久顺序节点:就是有顺序的持久节点,节点特性和持久节点是⼀样的,只是额外特性表现在顺序上。顺序特性实质是在创建节点的时候,会在节点名后⾯加上⼀个数字后缀,来表示其顺序。
    临时节点:⽣命周期和客户端会话绑在⼀起,客户端会话结束,节点 会被删除掉。与持久性节点不同的是,临时节点不能创建⼦节点
    临时顺序节点:有顺序的临时节点,和持久顺序节点相同,在其创建的时候会在名字后⾯加上数字 后缀。

事务id

⾸先,先了解,事务是对物理和抽象的应⽤状态上的操作集合。往往在现在的概念中,狭义上的事务通 常指的是数据库事务,⼀般包含了⼀系列对数据库有序的读写操作,这些数据库事务具有所谓的ACID特 性,即原⼦性(Atomic)、⼀致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
⽽在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,我们也称之为事务操作或更新操 作,⼀般包括数据节点创建与删除、数据节点内容更新等操作。对于每⼀个事务请求,ZooKeeper都会为其分配⼀个全局唯⼀的事务ID,⽤ ZXID 来表示,通常是⼀个 64 位的数字。每⼀个 ZXID 对应⼀次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些更新操作请求的全局顺序。

节点状态信息

  • cZxid 就是 Create ZXID,表示节点被创建时的事务ID。
  • ctime 就是 Create Time,表示节点创建时间。
  • mZxid 就是 Modified ZXID,表示节点最后⼀次被修改时的事务ID。
  • mtime 就是 Modified Time,表示节点最后⼀次被修改的时间。
  • pZxid 表示该节点的⼦节点列表最后⼀次被修改时的事务 ID。只有⼦节点列表变更才会更新 pZxid,
  • ⼦节点内容变更不会更新。
  • cversion 表示⼦节点的版本号。
  • dataVersion 表示内容版本号。
  • aclVersion 标识acl版本
  • ephemeralOwner 表示创建该临时节点时的会话 sessionID,如果是持久性节点那么值为 0
  • dataLength 表示数据⻓度。
  • numChildren 表示直系⼦节点数。

Watcher(数据变更通知)

Zookeeper使⽤Watcher机制实现分布式数据的发布/订阅功能
⼀个典型的发布/订阅模型系统定义了⼀种 ⼀对多的订阅关系,能够让多个订阅者同时监听某⼀个主题对象,当这个主题对象⾃身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。
在 ZooKeeper 中,引⼊了 Watcher 机制来实现这种分布式的通知功能。ZooKeeper 允许客户端向服务端注册⼀个 Watcher 监听,当服务端的⼀些指定事件触发了这个 Watcher,那么就会向指定客户端发送⼀个事件通知来实现分布式的通知功能。

Zookeeper的Watcher机制主要包括客户端线程、客户端WatcherManager、Zookeeper服务器三部 分。
具体⼯作流程为:客户端在向Zookeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程 从WatcherManager中取出对应的Watcher对象来执⾏回调逻辑。

ACL(权限管控)

Zookeeper作为⼀个分布式协调框架,其内部存储了分布式系统运⾏时状态的元数据,这些元数据会直接影响基于Zookeeper进⾏构造的分布式系统的运⾏状态,因此,如何保障系统中数据的安全,从⽽避免因误操作所带来的数据随意变更⽽导致的数据库异常⼗分重要,在Zookeeper中,提供了⼀套完善的ACL(Access Control List)权限控制机制来保障数据的安全。
权限就是指那些通过权限检查后可以被允许执⾏的操作。在ZooKeeper中,所有对数据的操作权限分为以下五⼤类:
·CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建⼦节点。 · DELETE(D):
⼦节点的删除权限,允许授权对象删除该数据节点的⼦节点。 · READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或⼦节点列表等。 · WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进⾏更新操作。 · ADMIN(A):数据节点的管理权限,允许授权对象对该数据节点进⾏ ACL 相关的设置操作。

ZooKeeper命令⾏操作

./zkcli.sh	连接本地的zookeeper服务器
./zkCli.sh -server ip:port 连接指定的服务器

连接成功后,可以通过help指令来查看帮助

创建节点

create [-s][-e] path data acl
其中,-s或-e分别指定节点特性,顺序或临时节点,若不指定,则创建持久节点;acl⽤来进⾏权限控制

读取节点

ls	path
其中,path表示的是指定数据节点的节点路径
get path
可以获取Zookeeper指定节点的数据内容和属性信息

更新节点

set path data [version]

删除节点

delete path [version]

zk API使用

导入依赖

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>

建立会话

private static CountDownLatch countDownLatch=new CountDownLatch(1);
    //建立会话
    public static void main(String[] args) throws IOException, InterruptedException {
        ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new CreateSession());
        System.out.println(zooKeeper.getState());
        //通过计数工具类CountDownLatch:不让main方法结束,让线程等待阻塞;
        countDownLatch.await();
        System.out.println("会话建立了");
    }
    //回调方法,作用:处理来自服务器端的时间watcher通知
    @Override
    public void process(WatchedEvent watchedEvent) {
        //SyncConnected 事件
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            //解除主程序在countDownLatch的等待阻塞
            System.out.println("process执行了");
            countDownLatch.countDown();
        }
    }

创建节点

在建立会话的基础上在回调方法中进行创建节点

//同步创建节点
    private static void createNodeSync() throws KeeperException, InterruptedException {
        /**
         *path	:节点创建的路径
         *data[]	:节点创建要保存的数据,是个byte类型的
         *acl	:节点创建的权限信息(4种类型)
         *ANYONE_ID_UNSAFE	: 表示任何⼈
         *AUTH_IDS	:此ID仅可⽤于设置ACL。它将被客户机验证的ID替
         换。
         *OPEN_ACL_UNSAFE	:这是⼀个完全开放的ACL(常⽤)-->
         world:anyone
         *CREATOR_ALL_ACL	:此ACL授予创建者身份验证ID的所有权限
         *createMode	:创建节点的类型(4种类型)
         *PERSISTENT:持久节点
         *PERSISTENT_SEQUENTIAL:持久顺序节点
         *EPHEMERAL:临时节点
         *EPHEMERAL_SEQUENTIAL:临时顺序节点
         String node = zookeeper.create(path,data,acl,createMode);
         */
        //持久节点
        String node_persistent = zooKeeper.create("/lg-server", "持久节点内容".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//        //临时节点
//        String node_ephemeral = zooKeeper.create("/lg-ephemeral", "临时节点内容".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//        //持久顺序节点
//        String node_persistent_sequential = zooKeeper.create("/lg-persistent_sequential", "持久顺序节点内容".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        System.out.println("持久节点"+node_persistent);
//        System.out.println("临时节点"+node_ephemeral);
//        System.out.println("持久顺序节点"+node_persistent_sequential);

    }

    //创建节点的方法
    //回调方法,作用:处理来自服务器端的时间watcher通知
    @Override
    public void process(WatchedEvent watchedEvent) {
        //SyncConnected 事件
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            //解除主程序在countDownLatch的等待阻塞
            try {
                createNodeSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

删除节点

 @Override
    public void process(WatchedEvent watchedEvent) {
        //SyncConnected 事件
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            //解除主程序在countDownLatch的等待阻塞
            try {
                deleteNodeSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //删除节点
    private void deleteNodeSync() throws KeeperException, InterruptedException {
        /*
        zooKeeper.exists(path,watch) :判断节点是否存在
        zookeeper.delete(path,version) : 删除节点
        */
        Stat exists = zooKeeper.exists("/lg-persistent/c1", false);
        System.out.println(exists==null?"该节点不存在":"该节点存在");
        if(exists!=null){
            zooKeeper.delete("/lg-persistent/c1",-1);
        }
        Stat exists2 = zooKeeper.exists("/lg-persistent/c1", false);
        System.out.println(exists2==null?"该节点不存在":"该节点存在");
    }

修改节点数据

 //更新数据节点内容
    private void updateNodeSync() throws KeeperException, InterruptedException {
        /*
        path:路径
        data:要修改的内容 byte[]
        version:为-1,表示对最新版本的数据进⾏修改
        zooKeeper.setData(path, data,version);
        */
        System.out.println(new String(zooKeeper.getData("/lg-persistent",false,null)));
        //修改数据,stat:状态信息对象
        Stat stat = zooKeeper.setData("/lg-persistent", "修改数据".getBytes(StandardCharsets.UTF_8), -1);
        System.out.println(new String(zooKeeper.getData("/lg-persistent",false,null)));
    }

    //创建节点的方法
    //回调方法,作用:处理来自服务器端的时间watcher通知
    @Override
    public void process(WatchedEvent watchedEvent) {
        //SyncConnected 事件
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            //解除主程序在countDownLatch的等待阻塞
            try {
                updateNodeSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

获取节点数据

public void process(WatchedEvent watchedEvent) {
        //子节点列表发生变化时,服务器会发出noteChildrenChanged事件通知,重新获取子节点列表,同时这个通知是一次性的,需要反复监听
        if(watchedEvent.getType()==Event.EventType.NodeChildrenChanged){
            try {
                getChildren();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        //SyncConnected 事件
        if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
            //解除主程序在countDownLatch的等待阻塞
            try {
                //获取节点数据
                getNodeData();
                //获取节点子节点列表
                getChildren();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //获取某个节点数据
    private void getNodeData() throws KeeperException, InterruptedException {
        /**
         *path	: 获取数据的路径
         *watch	: 是否开启监听
         *stat	: 节点状态信息
         *null: 表示获取最新版本的数据
         *zk.getData(path, watch, stat);
         */
        byte[] data = zooKeeper.getData("/lg-persistent", false, null);
        System.out.println(new String(data));
    }
    //获取某个节点的子节点列表方法
    public static void getChildren() throws KeeperException, InterruptedException {
        /*
        path:路径
        watch:是否要启动监听,当⼦节点列表发⽣变化,会触发监听
        zooKeeper.getChildren(path, watch);
        */
        List<String> children = zooKeeper.getChildren("/lg-persistent", true);
        System.out.println(children);
    }

Zookeeper-开源客户端(zkClient)

ZkClient是Github上⼀个开源的zookeeper客户端,在Zookeeper原⽣API接⼝之上进⾏了包装,是⼀个 更易⽤的Zookeeper客户端,同时,zkClient在内部还实现了诸如Session超时重连、Watcher反复注册 等功能。
添加依赖

<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>

创建会话

ZkClient zkClient=new ZkClient("localhost:2181");
        System.out.println("会话创建");

创建节点

ZkClient zkClient=new ZkClient("localhost:2181");
        System.out.println("会话创建");
        //创建节点 第一个参数为节点名称,第二个参数如果为true,则创建父节点再创建子节点
        zkClient.createPersistent("/lg-zkClient/c1",true);
        System.out.println("节点创建完成");

删除节点

ZkClient zkClient=new ZkClient("localhost:2181");
        System.out.println("会话创建");
        String path="/lg-zkClient/c1";
        zkClient.createPersistent(path+"/c11");
        //删除节点
        zkClient.deleteRecursive(path);
        System.out.println("递归删除成功");

修改节点数据

zkClient.writeData(path,"456");

获取节点数据

List<String> children = zkClient.getChildren("/lg-zkClient");

为客户端添加监听

zkClient.subscribeChildChanges("/lg-zkClient-get", new IZkChildListener() {
            /**
             *
             * @param s parentPath 父节点路径
             * @param list 变化后的子节点列表
             * @throws Exception
             */
            @Override
            public void handleChildChange(String s, List<String> list) throws Exception {
                System.out.println(s+"的子节点列表发生变化:"+list);
            }
        });

Curator客户端

curator是Netflix公司开源的⼀套Zookeeper客户端框架,和ZKClient⼀样,Curator解决了很多 Zookeeper 客 户 端 ⾮ 常 底 层 的 细 节 开 发 ⼯ 作 , 包 括 连 接 重 连 , 反 复 注 册 Watcher 和 NodeExistsException异常等,是最流⾏的Zookeeper客户端之⼀。从编码⻛格上来讲,它提供了基于Fluent的编程⻛格⽀持。
Fluent编程风格:在set方法添加当前对象的返回,那么再编码过程中,就支持链式编程,简化代码。

添加依赖

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>

创建会话

Curator的创建会话⽅式与原⽣的API和ZkClient的创建⽅式区别很⼤。Curator创建客户端是通过CuratorFrameworkFactory⼯⼚类来实现的。

 RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        //不使用fluent风格编程
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("localhost:2181", exponentialBackoffRetry);
        curatorFramework.start();
        System.out.println("会话被建立");
        //使用fluent风格编程 namespace设置了独立的命名空间 /base
        CuratorFramework base = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")
                .build();
        base.start();
        System.out.println("会话创建了");

创建节点

RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        //使用fluent风格编程 namespace设置了独立的命名空间 /base
        CuratorFramework base = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("lg-server")
                .build();
        base.start();
        System.out.println("会话创建了");
base.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/test", "init".getBytes(StandardCharsets.UTF_8));

删除节点

 RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        //使用fluent风格编程 namespace设置了独立的命名空间 /base
        CuratorFramework base = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")
                .build();
        base.start();
        System.out.println("会话创建了");
        String path="/lg-curator";
        base.delete().deletingChildrenIfNeeded().withVersion(-1).forPath(path);
        System.out.println("删除成功,删除的节点"+path);

修改节点

RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        //使用fluent风格编程 namespace设置了独立的命名空间 /base
        CuratorFramework base = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")
                .build();
        base.start();
        System.out.println("会话创建了");
        String path="/lg-curator";

        //获取节点数据内容及状态信息
        byte[] bytes = base.getData().forPath(path);
        System.out.println("获取到的数据"+new String(bytes));
        Stat stat = new Stat();
        base.getData().storingStatIn(stat).forPath(path);
        System.out.println("获取到的节点状态信息:"+stat);
        //更新节点信息
        int version = base.setData().withVersion(stat.getVersion()).forPath(path, "修改内容".getBytes(StandardCharsets.UTF_8)).getVersion();
        System.out.println("当前最新版本为:"+version);
        byte[] bytes1 = base.getData().forPath(path);
        System.out.println("修改后的数据为:"+new String(bytes1));
        //badVersionException
        base.setData().withVersion(stat.getVersion()).forPath(path,"修改内容22222".getBytes(StandardCharsets.UTF_8));

获取节点信息和数据

RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        //使用fluent风格编程 namespace设置了独立的命名空间 /base
        CuratorFramework base = CuratorFrameworkFactory.builder().connectString("localhost:2181").sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
//                .namespace("base")
                .build();
        base.start();
        System.out.println("会话创建了");
        String path="/lg-curator/c1";
//        String s = base.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes(StandardCharsets.UTF_8));
//        System.out.println("节点递归创建成功,路径为"+s);
        List<String> strings = base.getChildren().forPath("/lg-server");
        System.out.println(strings);
        String path1="/lg-server/localhost:8088/status";
        Stat stat = new Stat();
        base.getData().storingStatIn(stat).forPath(path1);
        System.out.println("获取到的节点状态信息:"+stat);

以上是关于Zookeeper基本使用的主要内容,如果未能解决你的问题,请参考以下文章

颗粒归仓--Zookeeper基本概念

c_cpp Robolution基本代码片段

Zookeeper基本使用

Zookeeper基本使用

Zookeeper基本使用

Zookeeper基本使用