zookeeper 介绍、基础命令、javaAPI

本文详细介绍Zookeeper客户端API的使用方法,包括创建会话、创建节点、获取子节点列表、读取节点内容及删除节点等核心操作。通过示例代码演示各种方法的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转  https://blog.youkuaiyun.com/column/details/14599.html

简介

如果你还处于单机时代,那么你将很少用到Zookeeper,很多更好的方案可以帮助你解决问题。一旦涉及到分布式应用,或许在每做一个决定的时候都要想一想,是否可以使用Zookeeper来实现。

Zookeeper是Apache Hadoop的一个子项目,主要是用来解决分布式应用中经常遇到的一些数据管理问题。下图列举了一些可能会遇到的场景。

Zookeeper使得分布式程序能够通过一个共享的、树形结构的名字空间来进行相互协调。组成这个树形结构的数据节点被称作ZNode,它们之间的层级关系就像文件系统的目录结构一样。 

zookeeper主要有以下角色:

角色说明
Leader(领导者)为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
Follower(跟随者)为客户端提供读服务,如果是写服务则转发给Leader。在选举过程中参与投票。
Observe(观察者)为客户端提供读服务器,如果是写服务则转发给Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于zookeeper3.3系列新增的角色。
client(客户端)连接zookeeper服务器的使用着,请求的发起者。独立于zookeeper服务器集群之外的角色。

 

Zookeeper的客户脚本及命令

1> 启动Zookeeper : 

      zkServer.sh start 
2> zkCli.sh 为连接zookeeper服务器的客户端脚本(默认连接本地的服务器): 

      sh zkCli.sh

3> zkCli.sh 为连接zookeeper服务器的客户端脚本(指定服务器): 

     sh zkCli.sh  -server ip:port

4> 创建操作:

    create [-s] [-e] path data acl

其中,-s或-e分别指定节点为顺序或临时节点,默认情况下创建的为持久节点。 
执行如下命令:

create /zk-create-demo 'hello world'

通过上面的命令创建了一个名字叫zk-create-demo的节点,其中节点的内容为‘hello world’。其中acl命令是用来做权限控制的,此例中没有传递此参数,默认不作任何权限控制。

5>查看ls  删除 delete  更新 set 获取值 get

 

Zookeeper客户端API

1. 创建会话

pom 文件:

<dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>版本</version>
</dependency>

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)

其中,connectString表示要连接的zookeeper服务器地址列表,格式为:192.168.0.1:2181。支持多个地址拼接,中间用逗号分隔。其中地址后面还可以拼接上zookeeper的操作路径,比如:192.168.0.1:2181/zk/test。

sessionTimeout:会话超时时间,单位“毫秒”。通过心跳来监测会话的有效性。

watcher:监听节点的状态变化,如果发生变化则通知此watcher,做出相应处理。如果不需要监听,则可设置为null。

public class TestSession implements Watcher{

    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) throws IOException {
        Long startTime = new Date().getTime();
        ZooKeeper zooKeeper = new ZooKeeper("192.168.0.1:2181",5000,new TestSession());
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("创建连接花费时间:" + (new Date().getTime() - startTime) + "ms");
        System.out.println("连接状态:" + zooKeeper.getState());
    }

    public void process(WatchedEvent event) {
        System.out.println("Receive watcher event:" + event);
        if(Event.KeeperState.SyncConnected == event.getState()){
            countDownLatch.countDown();
        }
    }
}

由于Zookeeper客户端和服务器创建会话是异步过程,因此使用CountDownLatch来阻塞线程,等待服务器创建完成,并发送事件通知。

2.创建节点

Zookeeper提供了两个创建数据节点的方法。

同步创建数据节点方法:

public String create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode)
        throws KeeperException, InterruptedException

异步创建数据节点方法:

public void create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode,  StringCallback cb, Object ctx)

参数说明

参数名称说明
path创建节点的路径,比如:/zk-test-create。
data[]字节数组,创建节点初始化内容。使用者需自己进行序列化和反序列化。复杂对象可使用 Hessian或Kryo进行进行序列化和反序列化。
acl节点的acl策略
createMode节点类型,类型定义在枚举CreateMode中:(1)PERSISTENT:持久;(2)PERSISTENT_SEQUENTIAL:持久顺序;(3)EPHEMERAL:临时;(4)EPHEMERAL_SEQUENTIAL:临时顺序。
cb异步创建方法参数。注册的回调函数,需实现StringCallback接口。主要针对public void processResult(int rc, String path, Object ctx, String name);接口进行重写。数据节点创建完成之后,会调用此方法进行业务逻辑处理。
ctx异步创建方法参数。用户传递一个对象,可以在回调方法执行时使用,通常是放一个上下文(Context)信息

创建节点demo

下面以具体代码来说明不同方法的使用,针对不同的方法有相应的注释说明:

package com.secbro.learn;

import org.apache.zookeeper.*;

import java.util.concurrent.CountDownLatch;

/**
 * Created by zhuzs on 2017/3/23.
 */
public class TestCreateNode implements Watcher {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
        ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new TestCreateNode());
        countDownLatch.await();

        // 同步创建临时节点
        String ephemeralPath = zooKeeper.create("/zk-test-create-ephemeral-", "123".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        System.out.println("同步创临时建节点成功:" + ephemeralPath);

        // 同步创建临时顺序节点
        String sequentialPath = zooKeeper.create("/zk-test-create-sequential-", "456".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("同步创建临时顺序节点成功:" + sequentialPath);

        // 异步创建临时节点
        zooKeeper.create("/zk-test-create-async-ephemeral-", "abc".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, new MyStringCallBack(), "我是传递内容");

        // 异步创建临时顺序节点
        zooKeeper.create("/zk-test-create-async-sequential-","def".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,new MyStringCallBack(),"我是传递内容");

        Thread.sleep(10000); // 验证等待回调结果使用,可根据实际情况自行调整
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            countDownLatch.countDown();
        }
    }
}

class MyStringCallBack implements AsyncCallback.StringCallback {

    public void processResult(int rc, String path, Object ctx, String name) {
        System.out.println("异步创建回调结果:状态:" + rc +";创建路径:" +
                path + ";传递信息:" + ctx + ";实际节点名称:" + name);
    }
}

输出结果为

同步创临时建节点成功:/zk-test-create-ephemeral-
同步创建临时顺序节点成功:/zk-test-create-sequential-0000000023
异步创建回调结果:状态:0;创建路径:/zk-test-create-async-ephemeral-;传递信息:我是传递内容;实际节点名称:/zk-test-create-async-ephemeral-
异步创建回调结果:状态:0;创建路径:/zk-test-create-async-sequential-;传递信息:我是传递内容;实际节点名称:/zk-test-create-async-sequential-0000000025
  •  

根据上面的代码和结果,很容易得知不同方法的使用方式。

注意事项

(1)Zookeeper不支持递归创建数据节点,无法在父节点不存在的情况下创建子节点。否则会抛出类似以下异常:

(2)如果节点已经存在,再创建同名节点,会抛出NodeExistsException。 
(3)关于权限控制,如果没有特殊要求,可按照上面例子中直接设置为ZooDefs.Ids.OPEN_ACL_UNSAFE,表明之后对节点的任何操作都不受权限控制。

StringCallback接口相关

StringCallback接口集成了AsyncCallback接口,来实现回调时的业务处理。其中AsyncCallback接口还包8个回调接口:StatCallback、DataCallback、ACLCallback、ChildrenCallback、Children2Callback、VoidCallback、MultiCallback、StringCallback。可以在不同的异步接口中实现不同的回调接口。

StringCallback接口的public void processResult(int rc, String path, Object ctx, String name)方法。 
从上面的实例中已经可以看出。 
int rc为服务器的响应码,0表示调用成功,-4表示连接已断开,-110表示指定节点已存在,-112表示会话已过期。 
String path调用create方法时传入的path。 
Object ctx调用create方法时传入的ctx。 
String name创建成功的节点名称。

3. 获取子节点列表方法

Zookeeper原生客户端API提供了以下8中获取子节点列表的方法,每个方法的使用说明参考注释内容:

/**
 * 返回指定路径下面的子节点列表。
 * 如果watcher不为null,并且调用成功(没有异常),会将watcher注册在指定的path
 * 上。当path(父节点)被删除或者path下面创建/删除子节点,将触发通知watcher。
 * 
 * 返回结果列表不保证有序性。
 */
public List<String> getChildren(final String path, Watcher watcher)
/**
 * 使用说明同上一个方法。
 */
public List<String> getChildren(String path, boolean watch)
  •  
/**
 * getChildren(String, Watcher)的异步版本
 */
public void getChildren(final String path, Watcher watcher,
            ChildrenCallback cb, Object ctx)
  •  
/**
 * getChildren(String, boolean)的异步版本
 */
public void getChildren(String path, boolean watch, ChildrenCallback cb,
            Object ctx)
  •  
/**
 * 为指定的路径返回stat和子节点列表。
 * 
 */
public List<String> getChildren(final String path, Watcher watcher,
            Stat stat)
  •  
/**
 * 使用说明同上一个方法。
 */
public List<String> getChildren(String path, boolean watch, Stat stat)
  •  
/**
 * getChildren(String, Watcher, Stat)的异步版本
 */
public void getChildren(final String path, Watcher watcher,
            Children2Callback cb, Object ctx)
  •  
/**
 * getChildren(String, boolean, Stat)的异步版本
 */
public void getChildren(String path, boolean watch, Children2Callback cb,
            Object ctx)
  •  

参数说明

参数名说明
path指定数据节点的路径,获取该节点下面的子节点
watcher注册在path上的Watcher。path被删除或者path下面的创建/删除节点,向客户端发送通知。可为null
watch表示是否需要注册一个watcher。true:注册默认watcher,false:不需要注册watcher
cb注册一个异步的回调函数
ctx用户传递信息
stat指定数据节点状态信息。传入旧stat,方法执行过程中会将其替换为新stat对象。

具体案例

package com.secbro.learn;

import org.apache.zookeeper.*;

import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 获得子节点列表的方法
 * Created by zhuzs on 2017/3/26.
 */
public class TestGetChildrenMethod implements Watcher {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zooKeeper;
    public static void main(String[] args) throws Exception {
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new TestGetChildrenMethod());
        countDownLatch.await();

        // 创建父节点/test
        zooKeeper.create("/test", "123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        // 在父节点/test下面创建a1节点
        zooKeeper.create("/test/a1", "456".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

        // 同步获得结果
        List<String> childrenList = zooKeeper.getChildren("/test", true);
        System.out.println("同步getChildren获得数据结果:" + childrenList);

        // 异步获得结果
        zooKeeper.getChildren("/test",true,new MyChildren2Callback(),null);

        // 在父节点/test下面创建a2节点
        zooKeeper.create("/test/a2", "456".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

        Thread.sleep(10000);

    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if(Event.EventType.None == event.getType() && null == event.getPath()){ // 连接时的监听事件
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeChildrenChanged){ // 子节点变更时的监听
                try {
                    System.out.println("重新获得Children,并注册监听:" + zooKeeper.getChildren(event.getPath(),true));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class MyChildren2Callback implements AsyncCallback.Children2Callback{

    public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
        System.out.println("异步获得getChildren结果,rc=" + rc
                + ";path=" + path + ";ctx=" + ctx + ";children=" + children +";stat=" + stat);
    }
}

输出结果:

同步getChildren获得数据结果:[a1]
异步获得getChildren结果,rc=0;path=/test;ctx=null;children=[a1];stat=14277,14277,1490498559704,1490498559704,0,1,0,0,3,1,14278

重新获得Children,并注册监听:[a1, a2]

上面的例子,先创建了一个父节点test,然后在其下面创建了a1子节点,并注册监听。当父节点test下面创建了新的a2子节点之后,监听事件触发。但此时需要重新主动获取该父节点下面的子节点,并继续注册监听事件。

上面只提供了新增子节点的案例,其实删除子节点或删除父节点同样会触发监听事件。

4. 读取节点内容方法介绍

Zookeeper提供了两个方法来获取节点内容,同步获取和异步获取:

public byte[] getData(String path, boolean watch, Stat stat)
public void getData(final String path, Watcher watcher,
            DataCallback cb, Object ctx)

参数说明

参数说明
path指定数据节点的路径,获取该节点下面的子节点
watcher注册在path上的Watcher。节点变更会通知会向客户端发起通知。
stat指定数据节点状态信息。传入旧stat,方法执行过程中会将其替换为新stat对象。
watch表示是否需要注册一个watcher。true:注册默认watcher,false:不需要注册watcher
cb注册一个异步回调函数
ctx传递上下文信息

具体案例

同步方法

package com.secbro.learn;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

/**
 *  读取节点数据
 * Created by zhuzs on 2017/3/27.
 */
public class TestGetData implements Watcher{
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zooKeeper;
    private static Stat stat = new Stat();

    public static void main(String[] args) throws Exception{
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new TestGetData());
        countDownLatch.await();

        String path = "/test-get-data";
        zooKeeper.create(path,"123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

        System.out.println("同步读取节点内容:" + new String(zooKeeper.getData(path,true,stat)));
        System.out.println("同步读取Stat:czxid=" + stat.getCzxid()
                + ";mzxid=" + stat.getMzxid() + ";version="  + stat.getVersion());

        zooKeeper.setData(path,"123".getBytes(),-1);
        Thread.sleep(10000);
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if(Event.EventType.None == event.getType() && null == event.getPath()){ // 连接时的监听事件
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged){ // 子节点内容变更时的监听
                try {
                    System.out.println("监听获得通知内容:data="
                            + new String(zooKeeper.getData(event.getPath(),true,stat)));
                    System.out.println("监听获得通知Stat:czxid=" + stat.getCzxid()
                            + ";mzxid=" + stat.getMzxid() + ";version="  + stat.getVersion());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

同步读取节点内容:123
同步读取Stat:czxid=14700;mzxid=14700;version=0
监听获得通知内容:data=123
监听获得通知Stat:czxid=14700;mzxid=14701;version=1

代码的基本逻辑为创建一个临时节点,然后读取临时节点内容,并注册监听,当节点变化(内容变化或版本信息变化),触发监听事件,获取最新的节点信息。

异步方法

package com.secbro.learn;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

/**
 *  读取节点数据
 * Created by zhuzs on 2017/3/27.
 */
public class TestGetData implements Watcher{
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zooKeeper;
    private static Stat stat = new Stat();

    public static void main(String[] args) throws Exception{
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new TestGetData());
        countDownLatch.await();

        String path = "/test-get-data";
        zooKeeper.create(path,"123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        // 异步读取节点内容
        zooKeeper.getData(path,true,new MyDataCallback(),null);

        zooKeeper.setData(path,"123".getBytes(),-1);

        Thread.sleep(10000);
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if(Event.EventType.None == event.getType() && null == event.getPath()){ // 连接时的监听事件
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged){ // 子节点内容变更时的监听
                try {
                    System.out.println("监听获得通知内容:data="
                            + new String(zooKeeper.getData(event.getPath(),true,stat)));
                    System.out.println("监听获得通知Stat:czxid=" + stat.getCzxid()
                            + ";mzxid=" + stat.getMzxid() + ";version="  + stat.getVersion());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyDataCallback implements AsyncCallback.DataCallback{

    public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
        System.out.println("异步返回结果:rc=" + rc + ";path=" + path + ";data=" + new String(data));
        System.out.println("异步读取Stat:czxid=" + stat.getCzxid()
                + ";mzxid=" + stat.getMzxid() + ";version="  + stat.getVersion());
    }
}

运行结果:

异步返回结果:rc=0;path=/test-get-data;data=123
异步读取Stat:czxid=14704;mzxid=14704;version=0
监听获得通知内容:data=123
监听获得通知Stat:czxid=14704;mzxid=14705;version=1

异步方法与同步方法业务逻辑基本相同,区别点在于将同步获取改为异步获取。

更新操作中的版本参数如果为-1,则表示更新操作针对任何版本均可。当更新版本不为-1,且不等于节点的目前版本,则更新失败

5. 删除节点

同步删除:

public void delete(final String path, int version)
  •  

异步删除:

public void delete(final String path, int version, VoidCallback cb,
            Object ctx)

参数说明

参数说明
path操作节点路径
version指定更新节点的数据版本。当为-1时表示任何版本
cb注册一个回调函数
ctx传递上下文信息

其他说明

1、版本操作同修改节点使用方法。 
2、如果一个节点下面有子节点,需先删除子节点,然后才能删除父节点。

 

zookeeper递归删除所有节点

public void deleteNode(String path) throws KeeperException, InterruptedException {

    String pathFull = path;
    if(path.equalsIgnoreCase("/zookeeper")){
        return;
    }
    List<String> childNodeList = zooKeeper.getChildren(path,false);
    if(childNodeList.size()>0){
        for(String str :childNodeList){
            if(pathFull.equals("/")){
                deleteNode(pathFull+str);
            }else {
                deleteNode(pathFull+"/"+str);
            }

        }
    }else{
        zooKeeper.delete(path,-1);
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值