zookeeper

概念:一个分布式协调技术,高性能的,开源的分布式系统协调服务。

zookeeper=类似unix文件系统+通知机制+Znode节点

下载地址:https://zookeeper.apache.org/releases.html

功能:

  1. 统一命名服务
  2. 配置管理
  3. Java操作API

一、安装

bin目录下

启动:./zkServer.sh start

暂停:./zkServer.sh stop

客户端: ./zkCli.sh

退出:quit

注意:运行需要Java环境。

1、helloWorld

文件系统:所使用的数据模型风格很像文件系统的目录树结构,简单来说,有点类似windows中注册表的结构

有名称,有树节点,有Key(键)/Value(值)对的关系。

可以看做一个树形结构的数据库,分布在不同的机器上做名称管理。

ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。
很显然zookeeper集群自身维护了一套数据结构。这个存储结构是一个树形结构,每一个znode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。

二、数据模型Znode节点深入

1、znode的数据模型

Znode维护了一个stat结构,这个stat包含数据变化的版本号、访问控制列表变化、还有时间戳。版本号和时间戳一起,可让Zookeeper验证缓存和协调更新。每次znode的数据发生了变化,版本号就增加。

ZooKeeper的结构体

  1. czxid- 引起这个znode创建的zxid,创建节点的事务的zxid(ZooKeeper Transaction Id)
  2. ctime - znode被创建的毫秒数(从1970年开始)
  3. mzxid - znode最后更新的zxid
  4. mtime - 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子节点数量

总结:

zookeeper内部维护了一套类似UNIX的树形数据结构:由znode构成的集合,

znode的集合又是一个树形结构,每一个znode又有很多属性进行描述。 Znode = path + data + Stat

2、znode中的存在类型

  1. PERSISTENT-持久化目录节点 create 默认
  2. PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 create -s
  3. EPHEMERAL-临时目录节点 create -e
  4. EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 create -s -e

PERSISTENT-持久化节点:创建这个节点的客户端在与zookeeper服务的连接断开后,这个节点也不会被删除

PERSISTENT_SEQUENTIAL-持久化顺序编号节点:当客户端请求创建这个节点A后,zookeeper会根据parent-znode的zxid状态,为这个A节点编写一个全目录唯一的编号(这个编号只会一直增长)。当客户端与zookeeper服务的连接断开后,这个节点也不会被删除。

EPHEMERAL-临时目录节点:创建这个节点的客户端在与zookeeper服务的连接断开后,这个节点(还有涉及到的子节点)就会被删除。

EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:当客户端请求创建这个节点A后,zookeeper会根据parent-znode的zxid状态,为这个A节点编写一个全目录唯一的编号(这个编号只会一直增长)。当创建这个节点的客户端与zookeeper服务的连接断开后,这个节点被删除。

三、基础命令和Java客户端操作

1、zkCli的常用命令操作

  1. ls
  2. ls2 查看当前节点数据并能看到更新次数等数据
  3. stat 查看节点状态
  4. set
  5. get
  6. create 默认无参持久化 --s含有序列 -e临时 可以同时用
  7. delete 删除无子节点的目录
  8. rmr 递归删除

2、四字命令

概念:zookeeper支持某些特定的四字命令,他们大多是用来查询ZK服务的当前状态及相关信息的,
通过telnet或nc向zookeeper提交相应命令,如:echo ruok | nc 127.0.0.1 2181

常用命令:

3、Java客户端操作

1、导入JAR包

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

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

2、log4j

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">


<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="log.console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n"/>
        </layout>
        <!--
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
           <param name="levelMin"   value="debug" />
           <param name="levelMax"   value="warn" />
           <param name="AcceptOnMatch" value="true" />
        </filter>-->

    </appender>

    <appender name="log.file" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="D:\atguigu4XML.log"/>
        <param name="Append" value="true"/>
        <param name="DatePattern" value="'.'yyyy-MM-dd"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n"/>
        </layout>
        <!-- <filter class="org.apache.log4j.varia.LevelRangeFilter">
           <param name="levelMin"   value="info" />
           <param name="levelMax"   value="info" />
           <param name="AcceptOnMatch" value="true" />
          </filter> -->
    </appender>
    <!--
    <logger name="com.atguigu" >
     <level value="info" />
     <appender-ref ref="log.console" />
     <appender-ref ref="log.file" />
    </logger>

    <logger name="com.atguigu.dao" >
     <level value="debug" />
     <appender-ref ref="log.console" />
     <appender-ref ref="log.file" />
    </logger>-->

    <!-- -->
    <root>
        <level value="info"/>
        <appender-ref ref="log.console"/>
        <appender-ref ref="log.file"/>
    </root>
</log4j:configuration>


3、java代码API操作zookeeper

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

import java.io.IOException;

/**
 * @Description:
 * @Auther: Tang
 * @Date: 2020/9/27 0027 下午 20:45
 */
public class WatchMore {

    private static final String CONNECTSTRING = "39.99.144.224:2181";
    private static final String PATH = "/tang";
    private static final int SESSION_TIMEOUT=20*1000;

    /**
     * 创建连接
     * @return
     * @throws IOException
     */

    public ZooKeeper startZK() throws IOException {
        return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

    /**
     * 关闭连接
     * @param zk
     * @throws InterruptedException
     */

    public void stopZK(ZooKeeper zk) throws InterruptedException {
        if (zk!=null){
            zk.close();
        }
    }

    /**
     * 创建zNode节点
     * @param zk
     * @param path
     * @param data
     * @throws KeeperException
     * @throws InterruptedException
     */

    public void createZNode(ZooKeeper zk , String path ,String data) throws KeeperException, InterruptedException {
        zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    /**
     * 获取zNode
     * @param zk
     * @param path
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */

    public String getZNode(ZooKeeper zk,String path) throws KeeperException, InterruptedException {
        String result = "";
        byte[] data = zk.getData(path, false, new Stat());
        result = new String(data);
        return result;
    }

    public static void main(String[] args) throws Exception {
        WatchMore watchMore = new WatchMore();
        ZooKeeper zk = watchMore.startZK();
        if (zk.exists(PATH,false)==null){
            watchMore.createZNode(zk,PATH,"helloZK");
            String zNode = watchMore.getZNode(zk, PATH);
            System.out.println("zNode:"+zNode);
        }else{
            System.out.println("this zNode is created");
        }
        watchMore.stopZK(zk);
    }
}

四、通知机制

1、通知机制:

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

2、观察者的功能watcher:

ZooKeeper 支持watch(观察)的概念。客户端可以在每个znode结点上设置一个观察。如果被观察服务端的znode结点有变更,那么watch就会被触发,这个watch所属的客户端将接收到一个通知包被告知结点已经发生变化,把相应的事件通知给设置过Watcher的Client端。(一句话:异步回调的触发机制

3、watch事件理解

  1. 一次触发:当数据有了变化时zkserver向客户端发送一个watch,它是一次性的动作,即触发一次就不再有效
  2. 发往客户端:Watches是异步发往客户端的,Zookeeper提供一个顺序保证:在看到watch事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序。
  3. 为数据设置watch:节点有不同的改动方式。可以认为ZooKeeper维护两个观察列表:数据观察和子节点观察。getData()和exists()设置数据观察。getChildren()设置子节点观察。此外,还可以认为不同的返回数据有不同的观察。getData()和exists()返回节点的数据,而getChildren()返回子节点列表。所以,setData()将为znode触发数据观察。成功的create()将为新创建的节点触发数据观察,为其父节点触发子节点观察。成功的delete()将会为被删除的节点触发数据观察以及子节点观察(因为节点不能再有子节点了),为其父节点触发子节点观察。
  4. 时序性和一致性:Watches是在client连接到Zookeeper服务端的本地维护,这可让watches成为轻量的,可维护的和派发的。当一个client连接到新server,watch将会触发任何session事件,断开连接后不能接收到。当客户端重连,先前注册的watches将会被重新注册并触发。、

4、多次观察代码

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

import java.io.IOException;

/**
 * @Description:
 * @Auther: Tang
 * @Date: 2020/9/27 0027 下午 20:45
 */
public class WatchMore {

    private static final String CONNECTSTRING = "39.99.144.224:2181";
    private static final String PATH = "/tang";
    private static final int SESSION_TIMEOUT=20*1000;

    private ZooKeeper zk;

    public ZooKeeper getZk() {
        return zk;
    }

    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }

    /**
     * 创建连接
     * @return
     * @throws IOException
     */

    public ZooKeeper startZK() throws IOException {
        return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

    /**
     * 关闭连接
     * @throws InterruptedException
     */

    public void stopZK() throws InterruptedException {
        if (zk!=null){
            zk.close();
        }
    }

    /**
     * 创建zNode节点
     * @param path
     * @param data
     * @throws KeeperException
     * @throws InterruptedException
     */

    public void createZNode(String path ,String data) throws KeeperException, InterruptedException {
        zk.create(path,data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    /**
     * 获取zNode
     * @param path
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */

    public String getZNode(String path) throws Exception {
        String result = "";
        byte[] data = zk.getData(path, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    triggerValue(path);
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, new Stat());
        result = new String(data);
        return result;
    }

    private void triggerValue(String path) throws KeeperException, InterruptedException {
        byte[] data = zk.getData(path, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    triggerValue(path);
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, new Stat());
        String result = new String(data);
        System.out.println("triggerValue:"+result);
    }

    public static void main(String[] args) throws Exception {
        WatchMore watchMore = new WatchMore();
        ZooKeeper zk = watchMore.startZK();
        watchMore.setZk(zk);
        if (zk.exists(PATH,false)==null){
            watchMore.createZNode(PATH,"AAA");
            String zNode = watchMore.getZNode(PATH);
            System.out.println("zNode:"+zNode);
        }else{
            System.out.println("this zNode is created");
        }
        Thread.sleep(Long.MAX_VALUE);
    }
}

五、ZooKeeper集群

1、伪分布式单机配置

说明

initLimit 是Zookeeper用它来限定集群中的Zookeeper服务器连接到Leader的时限

syncLimit 限制了follower服务器与leader服务器之间请求和应答之间的时限

配置步骤

  1. zookeeper-3.4.9.tar.gz解压后拷贝到/myzookeeper目录下并重新名为zk01,再复制zk01形成zk02、zk03,共计3份

  2. 进入zk01/02/03分别新建文件夹(mydata,mylog)

  3. 分别进入zk01-zk03各自的conf文件夹 新建zoo.cfg

  4. 编辑zoo.cfg
    设置自己的数据和log路径
    dataDir=/myzookeeper/zk01/mydata

    dataLogDir=/myzookeeper/zk01/mylog

    修改各自的clientPort

    在最后面添加server的列表

  5. 在各自mydata下面创建myid的文件,在里面写入server的数字

  6. 分别启动三个服务器

  7. zkCli连接server,带参数指定-server
    ./zkCli.sh -server 127.0.0.1:2193

    2191/2192/2193任意用客户端链接一台,会发现只需要有一个改变了,整个集群的内容自动一致性同步。

原文地址:https://www.cnblogs.com/xiaolaodi1999/p/13746676.html