ZooKeeper 是一个开源的分布式协调框架,主要用来解决分布式集群中应用系统的一致性问题。

zookeeper官网icon-default.png?t=N7T8https://zookeeper.apache.org/doc/current/index.html

目录

一、zookeeper的数据结构

二、下载zookeeper

三、windows上安装zookeeper 

四、linux上安装zookeeper

五、启动zookeeper

六、使用zookeeper客户端

创建节点

临时节点

容器节点

限时节点

设置节点数据

获取节点数据

事件监听机制

删除节点

获取节点状态

七、zookeeper集群架构

八、通过Java连接zookeeper

使用zookeeper客户端

springboot整合zookeeper


一、zookeeper的数据结构

zookeeper的结构类似于文件系统,其中每一个目录叫做zookeeper的节点(znode),znode有以下几种类型:

  • 持久化节点:节点不会被自动删除,会一直存在。
  • 临时节点:一次会话结束后,临时节点会被删除。在创建节点的时候通过参数-e指定为临时节点。
  • 顺序节点:一般情况下,节点名已经存在,是无法创建同名节点的,但是顺序节点允许同名,创建的同名节点会被按顺序编号。在创建节点的时候通过参数-s指定为顺序节点。
  • 容器节点:容器节点是一种特殊的znode,当他下面的所有子节点都被删除时,该节点会被删除,每60秒会删除一次空的容器节点。在创建节点的时候通过参数-c指定为顺序节点。

二、下载zookeeper

首先,需要下载zookeeper,点击链接打开zookeeper官网Apache ZooKeeper,在官网首页点击Getting Started下面的Download进入下载页面

 选择下载最新的稳定版本

然后点击任意链接开始下载zookeeperzook的de压缩包

如果下载很慢,博主提前下载好了之前的很多个稳定版本

百度网盘zookeeper下载链接icon-default.png?t=N7T8https://pan.baidu.com/s/1EU8UEi_MYLm6v8xIk1-BRw?pwd=gawp

三、windows上安装zookeeper 

下载完成后,解压到D盘,然后打开刚刚解压的zookeeper安装目录下的config目录,复制一份zoo_sample.cfg,然后重命名为zoo.cfg,修改里面的内容,dataLogDir是新增的,原来文件里没有

dataDir:zookeeper的安装目录\\data

dataLogDir:zookeeper的安装目录\\log

tickTime=2000

initLimit=10

syncLimit=5

clientPort=2181

dataDir=D:\\program\\apache-zookeeper-3.8.3-bin\\data

dataLogDir=D:\\program\\apache-zookeeper-3.8.3-bin\\log

然后在zookeeper的安装目录下新建两个目录data和log

经过以上的步骤,zookeeper就算安装完了。

启动zookeeper:双击zookeeper安装目录下的bin目录下的zkServer.bat

使用zookeeper:使用zookeeper的客户端,只需要双击zkCli.bat即可

四、linux上安装zookeeper

文章使用linux版本为Ubuntu-22.04.3,通过finalshell上传刚刚下载的压缩包到服务器,并解压。

比如上传到/usr目录下,通过以下命令解压

tar -zvxf apache-zookeeper-3.8.3-bin.tar.gz

然后进入zookeeper安装目录下的conf目录,复制一份zoo_example.cfg,建议文件名为zoo.cfg,因为启动zk服务器的时候默认通过zoo.cfg配置文件启动,不需要指定配置文件名。

cd apache-zookeeper-3.8.3-bin/

cp conf/zoo_sample.cfg conf/zoo.cfg

修改zoo.cfg的dataDir

dataDir=/usr/local/zookeeper

然后添加以下配置

# 默认是8080,因为8080可能被web应用使用,Springboot项目中tomcat的默认启动端口就是8080
admin.serverPort=9099

# 添加这个配置才能使用create -t ttl /node_name
extendedTypesEnabled=true

五、启动zookeeper

切换到zookeeper安装目录,通过以下命令之一启动zookeeper服务器

bin/zkServer.sh start

如果配置文件名不是zoo.cfg,需要指定配置文件名

bin/zkServer.sh start conf/配置文件名.cfg

输入bin/zkServer.sh会提示后面还可以带很多个命令

常用的几个命令

bin/zkServer.sh stop # 停止zookeeper

bin/zkServer.sh start # 启动zookeeper

bin/zkServer.sh status # 查看zookeeper状态

bin/zkServer.sh restart # 重启zookeeper

bin/zkServer.sh start-foreground # 在前台启动zookeeper

六、使用zookeeper客户端

要使用zk客户端的命令之前,需要通过以下命令启动zookeeper的客户端

bin/zkCli.sh

然后通过help命令查看zookeeper有哪些命令

创建节点

可以通过zk客户端提供的的create命令创建znode。

create /test

 可以看到这个命令后面可以带很多个参数,当不带任何参数时,该节点默认为持久化节点。

接下来介绍这些参数的作用:

命令参数作用
-s指定节点类型为顺序节点
-e指定节点类型为临时节点
-c指定节点类型为容器节点
-t指定节点的过期时间,使用该参数创建节点时,检查一下配置文件是否加了以下配置:extendedTypesEnabled=true

临时节点

演示创建临时节点效果,依次在终端输入以下命令

create -e /test

get /test

quit

然后重新连接客户端,再次获取/test节点的内容,提示节点不存在。

get /test

这时候通过get命令获取节点的数据,提示节点test不存在,是因为临时节点只在本次会话中有效,主动断开连接之后临时节点会被删除。

容器节点

create -c /node_name

创建一个容器节点,并创建其子节点

create -c /container

create /container/test

然后删除容器节点的唯一一个子节点

delete /container/test

然后等大概60秒,再次获取的时候,发现节点已经别自动删除了

限时节点

create -t 30 /test

注意,这里的节点并不会到了指定时间就删除,可能和znode过期删除策略有关。

设置节点数据

set [-s] [-v version] /node_name data

-s修改该节点,并查看节点的状态,相当于以下两个命令的组合

set /test abc

get -s /test

-v指定版本号,只有当前数据的版本号和指定版本一样才会修改成功

比如接着上面的命令,再修改一次/test,让它的版本号dataVersion变成2,然后再次修改时通过-v指定版本号为1,此时会提示该版本号不存在,版本号机制是CAS操作中为了避免ABA问题而设置的。

获取节点数据

get [-s] [-w] /node_name

参数说明:

可选参数-s表示查看节点状态信息;

可选参数-w表示给节点添加一个监听器,这个监听器只会生效一次。

事件监听机制

通过命令中指定的参数-w可以为创建/指定的节点添加一个监听器,监听节点的增删改操作,监听一次后将会移除该监听器。

通过addWatch命令可以给节点添加一个永久的监听器

 addWatch [-m mode] /node_name

mode表示监听的模式,有两种取值:

PERSISTENT:监听该节点的变化,以及子节点的增删,不能监听到子节点数据的修改;

PERSISTENT_RECURSIVE:(默认)监听该节点和其子节点的变化,包括子节点数据的变化;

删除节点

delete:可以指定版本号删除

delete -v version /node_name

deleteall:递归删除当前节点和它的所有子节点

deleteall /node_name

获取节点状态

stat [-w] /node_name

该命令返回的几个字段及其含义:

字段说明
cZxid
znode创建的事务id
ctime
节点创建时的时间戳
mZxid
znode被修改的事务id,每次对znode的修改都会更新mZxid
pZxid
表示该节点的子节点列表最后一次修改的事务ID,添加子节点或删除子节点就会影响子节点列表, 修改子节点的数据内容则不影响该ID
mtime节点最后一次修改时的时间戳

cversion

子节点的版本号,当znode的子节点有变化时,cversion 的值就会增加1。

dataVersion

数据版本号,每次对节点进行set操作,dataVersion的值都会增加1
ephemeralOwner
如果该节点为临时节点, ephemeralOwner值表示与该节点绑定的session id。如果不是,
ephemeralOwner值为0(持久节点)。
dataLength
节点数据的长度

numChildren

节点的直接子节点的数量

七、zookeeper集群架构

事务:对于create、setData、delete等有写操作的请求,则要统一转发给leader处理,leader需要决定编号、执行操作,这个过程称为事务。

zookeeper集群的几种角色:leader、follower、observer

  • leader:处理读写请求,保证集群事务处理的顺序性;
  • follower:只处理读请求,读请求会转发给Leader,参与集群Leader选举投票;
  • observer:只处理读请求,读请求会转发给Leader,不会参与集群Leader选举;

八、通过Java连接zookeeper

使用zookeeper客户端

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @author heyunlin
 * @version 1.0
 */
public class ZookeeperExample {

    public static void main(String[] args) {
        try {
            ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    System.out.println(event);
                }
            });
            String node = "/test";

            // 获取节点信息
            Stat stat = zooKeeper.exists(node, true);

            // 如果节点不存在,则创建
            if (stat == null) {
                zooKeeper.create(node, "123".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }

            // 重新获取节点信息
            stat = zooKeeper.exists(node, true);
            // 获取节点的dataVersion
            int version = stat.getVersion();

            // 获取节点数据
            byte[] data = zooKeeper.getData(node, true, null);
            System.out.println(new String(data));

            // 设置节点数据
            zooKeeper.setData(node, "789".getBytes(StandardCharsets.UTF_8), version);
            data = zooKeeper.getData(node, true, null);
            System.out.println(new String(data));
        } catch (IOException | KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
    }

}

springboot整合zookeeper

1、在IntelliJ IDEA里新建一个springboot项目,命名为zookeeper

2、在项目的pom.xml文件中引入zookeeper和相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.9</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>zookeeper</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

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

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

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3、修改application.xml配置文件,只需要指定项目启动端口号和zookeeper的服务器地址

server:
  port: 8085

zookeeper:
  host: localhost:2181

4、项目根目录下创建config包,新建一个zookeeper的配置类

package com.example.zookeeper.config;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author heyunlin
 * @version 1.0
 */
@Configuration
public class ZookeeperConfig {

    @Value("${zookeeper.host}")
    private String host;

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(host)
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(500, 5))
                .build();
        curatorFramework.start();

        return curatorFramework;
    }

}

5、使用zookeeper的API

package com.example.zookeeper.controller;

import com.example.zookeeper.restful.JsonResult;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author heyunlin
 * @version 1.0
 */
@RestController
@RequestMapping(path = "/zookeeper", produces = "application/json;charset=utf-8")
public class ZookeeperController {
    private final CuratorFramework curatorFramework;

    @Autowired
    public ZookeeperController(CuratorFramework curatorFramework) {
        this.curatorFramework = curatorFramework;
    }

    /**
     * 判断znode是否存在
     * @param node 节点名称
     */
    @RequestMapping(value = "/exist", method = RequestMethod.GET)
    public JsonResult<Stat> exist(String node) throws Exception {
        Stat stat = curatorFramework.checkExists().forPath(node);

        return JsonResult.success(null, stat);
    }

    /**
     * 创建一个znode
     * @param node 节点名称
     */
    @RequestMapping(value = "/create", method = RequestMethod.GET)
    public JsonResult<Void> create(String node) throws Exception {
        curatorFramework.create()
                .creatingParentContainersIfNeeded()
                /*
                创建模式:常用的有
                    PERSISTENT:持久化节点,客户端与zookeeper断开连接后,该节点依旧存在,只要不手动删除,该节点就会永远存在。
                    PERSISTENT_SEQUENTIAL:持久化顺序编号目录节点,客户端与zookeeper断开连接后,该节点依旧存在,只是zookeeper给该节点名称进行顺序编号。
                    EPHEMERAL:临时目录节点,客户端与zookeeper断开连接后,该节点被删除。
                    EPHEMERAL_SEQUENTIAL:临时顺序编号目录节点,客户端与zookeeper断开连接后,该节点被删除,只是zookeeper给该节点名称进行顺序编号。
                */
                .withMode(CreateMode.EPHEMERAL)
                .forPath(node);

        return JsonResult.success("创建成功");
    }

    /**
     * 设置znode节点的数据
     * @param node 节点名称
     * @param data 节点的数据
     */
    @RequestMapping(value = "/setData", method = RequestMethod.GET)
    public JsonResult<Void> setData(String node, String data) throws Exception {
        curatorFramework.setData().forPath(node, data.getBytes(StandardCharsets.UTF_8));

        return JsonResult.success("设置成功");
    }

    /**
     * 删除节点
     * @param node 节点名称
     */
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public JsonResult<Void> delete(String node) throws Exception {
        curatorFramework.delete().forPath(node);

        return JsonResult.success("删除成功");
    }

    /**
     * 删除节点及其子节点的数据
     * @param node 节点名称
     */
    @RequestMapping(value = "/deleteDeeply", method = RequestMethod.GET)
    public JsonResult<Void> deleteDeeply(String node) throws Exception {
        curatorFramework.delete().deletingChildrenIfNeeded().forPath(node);

        return JsonResult.success("删除成功");
    }

    /**
     * 获取节点的数据
     * @param node 节点名称
     */
    @RequestMapping(value = "/getData", method = RequestMethod.GET)
    public JsonResult<String> getData(String node) throws Exception {
        byte[] bytes = curatorFramework.getData().forPath(node);

        return JsonResult.success(null, new String(bytes));
    }

    /**
     * 获取当前节点的子节点数据
     * @param node 节点名称
     */
    @RequestMapping(value = "/getChildren", method = RequestMethod.GET)
    public JsonResult<List<String>> getChildren(String node) throws Exception {
        List<String> list = curatorFramework.getChildren().forPath(node);

        return JsonResult.success(null, list);
    }

}

好了,文章就分享到这里了~

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐