ZooKeeper基本操作

JavaGuide基础上做了一些增改

1. 前言

这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作,能满足日常工作的基本需要。

如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!

2. ZooKeeper 安装和使用

2.1. 使用Docker 安装 zookeeper

a.使用 Docker 下载 ZooKeeper

docker pull zookeeper:3.5.8

b.运行 ZooKeeper

docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8

2.2. 使用docker-compose 搭建 zookeeper集群

首先创建一个名为 docker-compose.yml 的文件, 其内容如下:

version: '2'
services:
    zoo1:
        image: zookeeper:3.5.8
        restart: always
        container_name: zoo1
        ports:
            - "2181:2181"
        environment:
            ZOO_MY_ID: 1
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
 
    zoo2:
        image: zookeeper:3.5.8
        restart: always
        container_name: zoo2
        ports:
            - "2182:2181"
        environment:
            ZOO_MY_ID: 2
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
 
    zoo3:
        image: zookeeper:3.5.8
        restart: always
        container_name: zoo3
        ports:
            - "2183:2181"
        environment:
            ZOO_MY_ID: 3
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181

这个配置文件会告诉 Docker 分别运行三个 zookeeper 镜像, 并分别将本地的 2181, 2182, 2183 端口绑定到对应的容器的2181端口上.ZOO_MY_IDZOO_SERVERS 是搭建 ZK 集群需要设置的两个环境变量, 其中 ZOO_MY_ID 表示 ZK 服务的 id, 它是1-255 之间的整数, 必须在集群中唯一. ZOO_SERVERS 是ZK 集群的主机列表.

注意这里有个坑:
ZOO_SERVERS后面的每一句后面有个 ;2181,在某个旧版本之前不需要加。
不加会报以下错误:
ZooKeeper JMX enabled by defaultUsing config: /conf/zoo.cfg
Client port not found in static config file. Looking in dynamic config file.
grep: : No such file or directory
Client port not found. Terminating.

接着我们在 docker-compose.yml 当前目录下运行:

docker-compose up -d

即可启动 ZK 集群了.执行上述命令成功后, 接着在另一个终端中运行 docker-compose ps 命令可以查看启动的 ZK 容器:

Name              Command               State                          Ports                        
----------------------------------------------------------------------------------------------------
zoo1   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2181->2181/tcp, 2888/tcp, 3888/tcp, 8080/tcp
zoo2   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2182->2181/tcp, 2888/tcp, 3888/tcp, 8080/tcp
zoo3   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2183->2181/tcp, 2888/tcp, 3888/tcp, 8080/tcp

查看状态:

nic@902server:~$ docker exec -it zoo1 zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

2.2. 连接 ZooKeeper 服务

a.进入ZooKeeper容器中
先使用 docker ps 查看 ZooKeeper 的 ContainerID,然后使用 docker exec -it ContainerID /bin/bash 命令进入容器中。

b.先进入 bin 目录,然后通过 ./zkCli.sh -server 127.0.0.1:2181命令连接ZooKeeper 服务

root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# ./zkCli.sh -server 127.0.0.1:2181

如果你看到控制台成功打印出如下信息的话,说明你已经成功连接 ZooKeeper 服务。
在这里插入图片描述

2.3. 常用命令演示

2.3.1. 查看常用命令(help 命令)

通过 help 命令查看 ZooKeeper 常用命令

2.3.2. 创建节点(create 命令)

通过 create 命令在根目录创建了 node1 节点,与它关联的字符串是"node1"

[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 "node1"

通过 create 命令在根目录创建了 node1 节点,与它关联的内容是数字 123

[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123
Created /node1/node1.1

创建短暂节点,当前客户端与服务器断开连接之后该节点会消失

[zk: 127.0.0.1:2181(CONNECTED) 18] create -e /temp "temp"
Created /temp

创建带序号的普通节点,当前根节点下已经有三个节点,所以序号从3开始

[zk: 127.0.0.1:2181(CONNECTED) 20] create -s /seq 1
Created /seq0000000003
[zk: 127.0.0.1:2181(CONNECTED) 21] create -s /seq 1
Created /seq0000000004
[zk: 127.0.0.1:2181(CONNECTED) 22] create -s /seq 1
Created /seq0000000005
[zk: 127.0.0.1:2181(CONNECTED) 23] ls /
[node1, seq0000000003, seq0000000004, seq0000000005, temp, zookeeper]
2.3.3. 更新节点数据内容(set 命令)
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
2.3.4. 获取节点的数据(get 命令)

get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 set 命令已经将节点数据内容改为 “set node1”。

[zk: 127.0.0.1:2181(CONNECTED) 7] get /node1
set node1

[zk: 127.0.0.1:2181(CONNECTED) 1] get -s /node1
222
cZxid = 0x2
ctime = Sun May 16 12:57:22 UTC 2021
mZxid = 0xf
mtime = Sun May 16 14:35:03 UTC 2021
pZxid = 0x12
cversion = 2
dataVersion = 4
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 2

监听节点数据变化,一次命令只能监听一次变化:

[zk: 127.0.0.1:2181(CONNECTED) 26] get -w /node1 
set node1
[zk: 127.0.0.1:2181(CONNECTED) 27] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1
2.3.5. 查看某个目录下的子节点(ls 命令)

通过 ls 命令查看根目录下的节点

[zk: 127.0.0.1:2181(CONNECTED) 37] ls /
[dubbo, ZooKeeper, node1]

通过 ls 命令查看 node1 目录下的节点

[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1
[node1.1]

ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径 path 下的所有子节点信息(列出 1 级,并不递归)

监听节点路径变化,添加子节点,一次命令只能监听一次变化:

[zk: 127.0.0.1:2181(CONNECTED) 3] ls -w /node1
[node1.1, node1.2]
[zk: 127.0.0.1:2181(CONNECTED) 4] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/node1
2.3.6. 查看节点状态(stat 命令)

通过 stat 命令查看节点状态

[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1
cZxid = 0x47
ctime = Sun Jan 20 10:22:59 CST 2019
mZxid = 0x47
mtime = Sun Jan 20 10:22:59 CST 2019
pZxid = 0x4a
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 1

上面显示的一些信息比如 cversion、aclVersion、numChildren 等等,我在上面 “znode(数据节点)的结构” 这部分已经介绍到。

2.3.7. 查看节点信息和状态(ls2 命令)

ls2 命令更像是 ls 命令和 stat 命令的结合。 ls2 命令返回的信息包括 2 部分:

  1. 子节点列表
  2. 当前节点的 stat 信息。
[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1
[node1.1]
cZxid = 0x47
ctime = Sun Jan 20 10:22:59 CST 2019
mZxid = 0x47
mtime = Sun Jan 20 10:22:59 CST 2019
pZxid = 0x4a
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 1
2.3.8. 删除节点(delete/rmr命令)

这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。

[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1

递归删除节点

[zk: 127.0.0.1:2181(CONNECTED) 3] rmr /node1

在后面我会介绍到 Java 客户端 API 的使用以及开源 ZooKeeper 客户端 ZkClient 和 Curator 的使用。

3. ZooKeeper Java客户端 Curator简单使用

Curator 是Netflix公司开源的一套 ZooKeeper Java客户端框架,相比于 Zookeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。

下面我们就来简单地演示一下 Curator 的使用吧!

Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前,请先将下面的依赖添加进你的项目。

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

3.1. 连接 ZooKeeper 客户端

通过 CuratorFrameworkFactory 创建 CuratorFramework 对象,然后再调用 CuratorFramework 对象的 start() 方法即可!

private static final int BASE_SLEEP_TIME = 1000;
private static final int MAX_RETRIES = 3;

// Retry strategy. Retry 3 times, and will increase the sleep time between retries.
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, MAX_RETRIES);
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
    // the server to connect to (can be a server list)
    .connectString("127.0.0.1:2181")
    .retryPolicy(retryPolicy)
    .build();
zkClient.start();

对于一些基本参数的说明:

  • baseSleepTimeMs:重试之间等待的初始时间
  • maxRetries :最大重试次数
  • connectString :要连接的服务器列表
  • retryPolicy :重试策略

3.2. 数据节点的增删改查

3.2.1. 创建节点

我们在 ZooKeeper常见概念解读 中介绍到,我们通常是将 znode 分为 4 大类:

  • 持久(PERSISTENT)节点 :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
  • 临时(EPHEMERAL)节点 :临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失 。并且,临时节点 只能做叶子节点 ,不能创建子节点。
  • 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001/node1/app0000000002
  • 临时顺序(EPHEMERAL_SEQUENTIAL)节点 :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。

你在使用的ZooKeeper 的时候,会发现 CreateMode 类中实际有 7种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。

a.创建持久化节点

你可以通过下面两种方式创建持久化的节点。

//注意:下面的代码会报错,下文说了具体原因
zkClient.create().forPath("/node1/00001");
zkClient.create().withMode(CreateMode.PERSISTENT).forPath("/node1/00002");

但是,你运行上面的代码会报错,这是因为的父节点node1还未创建。

你可以先创建父节点 node1 ,然后再执行上面的代码就不会报错了。

zkClient.create().forPath("/node1");

更推荐的方式是通过下面这行代码, **creatingParentsIfNeeded()**** 可以保证父节点不存在的时候自动创建父节点,这是非常有用的。**

zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node1/00001");

b.创建临时节点

zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001");

c.创建节点并指定数据内容

zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
zkClient.getData().forPath("/node1/00001");//获取节点的数据内容,获取到的是 byte数组

d.检测节点是否创建成功

zkClient.checkExists().forPath("/node1/00001");//不为null的话,说明节点创建成功
3.2.2. 删除节点

a.删除一个子节点

zkClient.delete().forPath("/node1/00001");

b.删除一个节点以及其下的所有子节点

zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
3.2.3. 获取/更新节点数据内容
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
zkClient.getData().forPath("/node1/00001");//获取节点的数据内容
zkClient.setData().forPath("/node1/00001","c++".getBytes());//更新节点数据内容
3.2.4. 获取某个节点的所有子节点路径
List<String> childrenPaths = zkClient.getChildren().forPath("/node1");

4. 案例:服务器动态上下线

在这里插入图片描述

package me.nic.zktest;

import org.apache.zookeeper.*;

import java.io.IOException;

/**
 * 服务端处理
 */
public class ServerHandler {

    private String connectStr;
    private int sessionTimeout;
    private ZooKeeper zkClient;

    private ServerHandler(String connectStr, int sessionTimeout) {
        this.connectStr = connectStr;
        this.sessionTimeout = sessionTimeout;
    }

    /**
     * 连接zookeeper
     *
     * @throws IOException
     */
    private void getConnect() throws IOException {
        zkClient = new ZooKeeper(connectStr, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

    /**
     * 注册server节点
     *
     * @param hostname
     */
    private void regist(String hostname) throws KeeperException, InterruptedException {
        if (zkClient != null) {
            String s = zkClient.create("/servers/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(s + "上线了");
        } else {
            System.out.println("未连接zookeeper集群");
        }
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
        String connectStr = "192.168.31.136:2181,192.168.31.136:2182,192.168.31.136:2183";
        ServerHandler serverHandle = new ServerHandler(connectStr, 2000);
        // 1. 连接zookeeper集群
        serverHandle.getConnect();
        // 2. 注册节点(args[0],从命令行参数中获取)
        serverHandle.regist(args[0]);
        // 3. 业务逻辑处理
        serverHandle.business();
    }
}
package me.nic.zktest;

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 客户端处理
 */
public class ClientHandler {

    private String connectStr;
    private int sessionTimeout;
    private ZooKeeper zkClient;

    private ClientHandler(String connectStr, int sessionTimeout) {
        this.connectStr = connectStr;
        this.sessionTimeout = sessionTimeout;
    }

    /**
     * 连接zookeeper
     *
     * @throws IOException
     */
    private void getConnect() throws IOException {
        zkClient = new ZooKeeper(connectStr, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    getChildren();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 注册server节点
     */
    private void getChildren() throws KeeperException, InterruptedException {
        if (zkClient != null) {
            List<String> children = zkClient.getChildren("/servers", true);
            List<String> hosts = new ArrayList<>();
            for (String child : children) {
                byte[] data = zkClient.getData("/servers/" + child, false, null);
                hosts.add(new String(data));
            }
            System.out.println(hosts);
        } else {
            System.out.println("未连接zookeeper集群");
        }
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
        String connectStr = "192.168.31.136:2181,192.168.31.136:2182,192.168.31.136:2183";
        ClientHandler clientHandler = new ClientHandler(connectStr, 2000);
        // 1. 连接zookeeper集群
        clientHandler.getConnect();
        // 2. 注册监听
        clientHandler.getChildren();
        // 3. 业务逻辑处理
        clientHandler.business();
    }
}

效果:
服务器上线: 启动两个服务端,分别传入hostname dubbo1、dubbo2,则在zookeeper中新增节点。客户端可监听到节点变化。
服务器下线: 由于新增的是临时节点,服务端与zookeeper断开之后,节点会被删除,客户端可监听到节点变化。

// 服务端
/servers/server0000000000上线了
/servers/server0000000001上线了

// 客户端
[]
[]
[dubbo1]
[dubbo2, dubbo1]
[dubbo2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值