zookeeper学习笔记

 

 

前言

根据尚硅谷java教程学习路线一路来到这的,前面的spring注解开发驱动源码部分真的是太折磨人了。这不,赶快来学习这个zookeeper教程了。
本文只编写到根据老师建议,完成会用部分,源码部分听从建议,等工作之后想要提升zookeeper这部分的时候再来看。

1 zookeeper介绍

1.1 概述

在这里插入图片描述

在这里插入图片描述

1.2 特点

在这里插入图片描述

1.3 数据结构

在这里插入图片描述

1.4 应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下
线、软负载均衡等。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5 如何下载

  1. 官网

  2. 下载截图
    在这里插入图片描述

  3. 如果要下载旧版本,操作如下图。
    在这里插入图片描述

  4. 选择3.5.7
    在这里插入图片描述

  5. 下载 Linux环境安装的 tar 包
    在这里插入图片描述

2 zookeeper本地安装

2.1 安装及配置

  1. mkdir /opt/softwaremkdir /opt/module前面一个是解压目录,后面一个是安装目录
  2. 检查系统中是否有jdk ========== java -version
  3. 将压缩包拷贝到 /opt/software目录下
  4. 解压到指定目录 ===== tar -zxvf apache-zookeeper-3.5.7-bin-tar-ge -C /opt/module
  5. 通常我们需要修改名称,修改为 mv apache-zookeeper-3.5.7 -bin/zookeeper-3.5.7
  6. /opt/module/zookeeper-3.5.7/conf 这个路径下的zoo_sample.cfg修改为zoo.cfg
    操作: mv zoo_sample.cfg zoo.cfg
  7. 打开zoo.cfg文件,修改dataDir路径,因为数据默认是存放到临时路径下的,临时路径/tmp这个会被定时清理,所以我们需要自己在 /opt/module/zookeeper-3.5.7/这个目录上创建zkData文件夹。然后在zoo.cfg```文件中修改如下内容:

dataDir=/opt/module/zookeeper-3.5.7/zkData

2.2 启动与关闭

  1. 启动服务器端
    在这里插入图片描述
  2. 查看进程是否启动
    在这里插入图片描述
  3. 查看状态

bin/zkServer.sh status

  1. 启动客户端
    在这里插入图片描述
  2. 退出客户端
    在这里插入图片描述
  3. 停止zookeeper
    在这里插入图片描述

2.3 配置参数解读

在这里插入图片描述
在这里插入图片描述

3 Zookeeper集群操作

3.1 集群安装

  1. 集群规划
    在这里插入图片描述
  2. 在另外两台虚拟主机上重复第二步的zookeeper本地安装。此处不再赘述。
  3. 编号2:192.168.119.100 编号3:192.168.119.110 编号3:192.168.119.120
  4. 在编号2的zkData目录下创建一个myid,具体操作:vim myid
    myid中的内容为:2,此后分别在编号3,编号4的相同zkData目录下创建,内容分别为3和4。
  5. 重命名 /opt/module/zookeeper-3.5.7/conf这个目录下的 zoo_sample.cfg为zoo.cfg

mv zoo_sample.cfg zoo.cfg

  1. 打开zoo.cfg文件,进行如下操作
  • 修改数据存储路劲配置
    dataDir=/opt/module/zookeeper-3.5.7/zkData
  • 增加如下配置:
    ##########cluster#############
    server.2=192.168.119.100:2888:3888
    server.3=192.168.119.110:2888:3888
    server.4=192.168.119.120:2888:3888

配置参数解读:
在这里插入图片描述

  1. 编号3 和编号4 进行同样操作。

3.2 批量启动脚本

在上一步中我们创建了zookeeper集群,但是每次启动是不是都要进入zookeeper安装目录中去进行执行启动,停止,查看状态脚本呢。如果有100台呢,所以我们可以去设置一个批量启动脚本来进行集群批量启动等状态。

  1. 在根目录下的bin目录中创建一个脚本文件,zk.sh
    在这里插入图片描述
  2. 在里面编写类容如下:

旧电脑:

#!/bin/bash

localip=192.168.1.131
case $1 in
"start"){
        for i in 192.168.76.100 192.168.76.110 192.168.76.120
                do
                        echo ------- zookeeper $i 启动 --------
                        if [ $i = $localip ]
                            then
                                /opt/module/zookeeper-3.5.7/bin/zkServer.sh start
                        else
                        		sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
                        fi
                done
}
;;
"stop"){
        for i in 192.168.76.100 192.168.76.110 192.168.76.120
                do
                        echo ------- zookeeper $i 停止 --------
                        if [ $i = $localip ]
                            then
                               /opt/module/zookeeper-3.5.7/bin/zkServer.sh stop
                        else
                                        sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
                        fi
                done
}
;;
"status"){
       for i in 192.168.76.100 192.168.76.110 192.168.76.120
                do
                        echo ------- zookeeper $i 状态信息 --------
                        if [ $i = $localip ]
                            then
                                /opt/module/zookeeper-3.5.7/bin/zkServer.sh status
                        else
                                        sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
                        fi
                done
}
;;
esac

192.168.119.100 192.168.119.110 192.168.119.120

下面是从网上找到的分享的有关该类脚本的设置,本文便是借鉴如下的。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
新电脑在结合网友分享和老师课件进行改动,可视为最终简洁版:

#!/bin/bash

case $1 in
"start"){
for i in 192.168.119.100 192.168.119.110 192.168.119.120
do
echo -------zookeeper $i 启动------------
sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"

done
};;
"stop"){
for i in 192.168.119.100 192.168.119.110 192.168.119.120
do
echo -------zookeeper $i 停止------------
sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"

done
};;
"status"){
for i in 192.168.119.100 192.168.119.110 192.168.119.120
do
echo -------zookeeper $i 状态------------
sshpass -p "123456" ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"

done
};;
esac
  1. 编辑完后,进行脚本执行权限 chmod 777 zk.sh
    启动脚本:zk.sh start
    停止脚本:zk.sh stop

3.3 选举机制(面试重点)

在这里插入图片描述

在这里插入图片描述

3.4 客户端操作

3.4.1 命令行操作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4.2 znode节点数据信息

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4.3 节点类型(持久/短暂/有序/无序)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4.4 监听器原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4.5 节点的删除与查看

在这里插入图片描述

3.5 IDEA操作

3.5.1 环境准备

  1. 创建一个maven工程:zookeeper
  2. 添加依赖
    依赖:

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>

在这里插入图片描述

3.5.2 创建客户端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Before – 表示在任意使用@Test注解标注的public void方法执行之前执行


public class zkClient {
    private String connectString = "192.168.119.100,192.168.119.110,192.168.119.120";
    private int sessionTimeout = 200000;
    private ZooKeeper zkClient;

//    @Test
    @Before
    public void  init() throws IOException {
       zkClient =  new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

				
				//如果没有下面代码,那么就只监听一次, 如有,则实时监听

                //监听代码
//                System.out.println("----------------------------------");
//                //监听实时变化
//                //监听根目录下
//                List<String> children = null;
//                try {
//                    children = zkClient.getChildren("/", true);
//
//                    for (String child: children) {
//                        System.out.println(child);
//                    }
//                    System.out.println("----------------------------------");
//
//                } catch (KeeperException e) {
//                    e.printStackTrace();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }

            }
        });
    }


    @Test
    public void create() throws InterruptedException, KeeperException {
        String nodeCreated = zkClient.create("/atguigu", "ss.avi".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }


    //监听实时变化
    @Test
    public void getChildren() throws InterruptedException, KeeperException {
        //true  开启监听
        //此处为true,监听使用的是init()方法中的new Watcher()监听
        List<String> children = zkClient.getChildren("/", true);

        for (String child: children) {
            System.out.println(child);
        }

        //设置延时(否则马上就执行完,再创建一个,控制台监听不到)
        Thread.sleep(Long.MAX_VALUE);
    }


    @Test
    public void exist() throws InterruptedException, KeeperException {
        Stat stat = zkClient.exists("/atguigu", false);
        System.out.println(stat==null? "not exist" : "exist");

    }
}

启动客户端报错:

org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /atguigu

网上查找解决办法 :
经过调试发现 private static final int sessionTimeout = 10000 中设置的sessionTimeout值太小,应增大此值,问题解决。
解释:sessionTimeout是会话超时时间,也就是当一个zookeeper超过该时间没有心跳,则认为该节点故障。所以,如果此值小于zookeeper的创建时间,则当zookeeper还未来得及创建连接,会话时间已到,因此抛出异常认为该节点故障。

在这里插入图片描述

3.6 客户端向服务端写数据流程

在这里插入图片描述
在这里插入图片描述

4 服务器动态上下线监听案例

4.1 需求

在这里插入图片描述

4.2 需求分析

在这里插入图片描述

4.3 具体实现

服务端代码实现

public class DistributeServer {

    private String connectString = "192.168.119.100,192.168.119.110,192.168.119.120";
    private int sessionTimeout = 200000;
    private ZooKeeper zk;
    //在启动的时候通过args传入主机名称
    public static void main(String[] args) throws Exception {
        DistributeServer server = new DistributeServer();

        //1.获取zk连接,将服务器与zk集群相连接
        server.getConnect();

        //2. 注册服务器到zk集群,注册其实就是创建/servers下路径,即创建节点
        server.regist(args[0]);

        //3.启动业务逻辑(不让其一下子就执行完)
        server.business();
        
    }

    //参数是传进来的,即我们在启动的时候传入主机名称
    private void regist(String hostname) throws InterruptedException, KeeperException {
        //创建临时带序号节点并且传入主机名
        //创建的是临时带序号的节点
        String create = zk.create("/servers/"+hostname, hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);


        System.out.println(hostname + " is online");
    }

    private void getConnect() throws IOException {

        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }

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

客户端代码实现

public class DistributeClient {
    private String connectString = "192.168.119.100,192.168.119.110,192.168.119.120";
    private int sessionTimeout = 200000;
    private ZooKeeper zk;
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        DistributeClient client = new DistributeClient();
        //1.获取zk连接
        client.getConnect();

        //2.监听/servers虾米子节点的增加和删除
        client.getServerList();

        //3.业务逻辑(睡觉)
        client.business();
    }

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

    private void getServerList() throws InterruptedException, KeeperException {

        //监听/servers这个节点下数据的变化,如果是true,表示使用的是getConnect()里面的监听器,否则需要我们自己创建
        List<String> children = zk.getChildren("/servers", true);

        ArrayList<String> servers = new ArrayList<>();

        //遍历子节点,取出主机名称,判断是否上下线,封装到一个集合中进行打印(封装到集合中是为了方便打印)

        for (String child : children) {
            byte[] data = zk.getData("/servers/" + child, false, null);
            servers.add(new String(data));
        }

        //打印
        System.out.println(servers);
    }

    private void getConnect() throws IOException {
         zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

                //如果这里不加,那么程序就只监听一次,在初始化这里加了监听之后,时刻进行监听
                try {
                    getServerList();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (KeeperException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

4.4 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 ZooKeeper分布式锁案例

5.1 需求

在这里插入图片描述
在这里插入图片描述

5.2 需求分析

在这里插入图片描述

5.3 原生Zookeeper 实现分布式锁案例

5.3.1 具体实现

public class DistributedLock {

    private final String connectString = "192.168.119.100:2081,192.168.119.110:2081,192.168.119.120:2081";
    private final int sessionTimeout = 200000;
    private final ZooKeeper zk ;

    private  CountDownLatch countDownLatch = new CountDownLatch(1);

    //等待前一步骤完成之后,下一步骤才进行执行
    private  CountDownLatch waitLatch = new CountDownLatch(1);

    //前一个节点的路径
    private String waitPath;
    private String currentMode;


    public DistributedLock() throws IOException, InterruptedException, KeeperException {

        //获取连接
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

                //监听器中判断释放的时机


            //countDownLatch 如果连接上zk 可以释放

                //判断监听的事件的状态是否是连接
                if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
                    //如果是,释放掉
                    countDownLatch.countDown();
                }

                //waitLatch 需要释放

                //如果是节点的删除而且节点路径还是前一个节点路径,证明前一个节点已经下线
                if(watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
                    waitLatch.countDown();
                }
            }
        });

        //countDownLatch作用:等待zk正常连接后,程序才往下执行,代码健壮性更强
        countDownLatch.await();

        //判断根节点/locks是否存在
        Stat stat = zk.exists("/locks", false);


        //对状态进行判断
        if(stat == null){

            //如果不存在,创建根节点
            zk.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
        }
    }


    //对zk加锁  ---其实就是在/locks目录下创建对应的临时带序号的节点
    public void zklock(){
        //创建对应的临时带序号节点
        try {

            currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            //wait-小会,让结果更清晰一些
            Thread.sleep(10);

            //判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听他序号前一个节点
            List<String> children = zk.getChildren("/locks", false);

            //如果children只有一个值,那就直接获取锁;如果有多个节点,需要判断,谁最小;
            if(children.size() == 1){

                //直接返回,获取锁
                return;
            }else{
                //有多个节点,需要取出来进行比较


                //排序
                Collections.sort(children);

                //获取节点名称 seq-00000000
                String thisNode = currentMode.substring("/locks/".length());

                //通过seq-00000000获取该节点在children集合的位置
                int index = children.indexOf(thisNode);

                //判断
                if(index == -1){
                    System.out.println("数据异常");
                }else if (index == 0){
                    //就一个节点,直接返回,获取到锁
                    return ;
                }else {
                    //如果不是只有一个节点,就需要进行监听了


                    //需要监听 他前一个节点变化
                    //waitpath:前一个节点的路径
                    waitPath = "/locks/"+children.get(index -1);

                    //监听
                    zk.getData(waitPath,true,new Stat());

                    //等待监听
                    waitLatch.await();

                    return;
                }
            }

        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    //解锁  --- 其实就是删除/locks目录下的临时节点
    public void unZklock(){

        //删除节点
        try {
            zk.delete(this.currentMode,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}

5.3.2 测试

public class CuratorLockTest {
    public static void main(String[] args) {

        //创建分布式锁1

        InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");

        //创建分布式锁2
        InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock1.acquire();
                    System.out.println("线程1 获取到锁");

                    lock1.acquire();
                    System.out.println("线程1 再次获取到锁");

                    Thread.sleep(5*1000);

                    lock1.release();
                    System.out.println("线程1 释放锁");

                    lock1.release();
                    System.out.println("线程1 再次释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();




        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock1.acquire();
                    System.out.println("线程2 获取到锁");

                    lock1.acquire();
                    System.out.println("线程2 再次获取到锁");

                    Thread.sleep(5*1000);

                    lock1.release();
                    System.out.println("线程2 释放锁");

                    lock1.release();
                    System.out.println("线程2 再次释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();

    }

    private static CuratorFramework getCuratorFramework() {

        //失败之后重试的时间和次数
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(20000, 3);

        //创建客户端
        //retryPolicy 失败之后重试次数和时间
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.119.100:2181,192.168.119.110:2181,192.168.119.120:2181")
                .sessionTimeoutMs(200000)
                .connectionTimeoutMs(20000)
                .retryPolicy(retry)
                .build();

        //客户端启动
        client.start();
        System.out.println("zookeeper 客户端启动成功");
        //返回客户端
        return client;

    }
}

在这里插入图片描述

5.4 Curator框架实现分布式锁案例

在这里插入图片描述

5.4.1 具体实现

[1]引入依赖

  <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.3.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.3.0</version>
        </dependency>

[2] 代码实现

public class CuratorLockTest {
    public static void main(String[] args) {

        //创建分布式锁1

        InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");

        //创建分布式锁2
        InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock1.acquire();
                    System.out.println("线程1 获取到锁");

                    lock1.acquire();
                    System.out.println("线程1 再次获取到锁");

                    Thread.sleep(5*1000);

                    lock1.release();
                    System.out.println("线程1 释放锁");

                    lock1.release();
                    System.out.println("线程1 再次释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();




        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock1.acquire();
                    System.out.println("线程2 获取到锁");

                    lock1.acquire();
                    System.out.println("线程2 再次获取到锁");

                    Thread.sleep(5*1000);

                    lock1.release();
                    System.out.println("线程2 释放锁");

                    lock1.release();
                    System.out.println("线程2 再次释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();

    }

    private static CuratorFramework getCuratorFramework() {

        //失败之后重试的时间和次数
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(20000, 3);

        //创建客户端
        //retryPolicy 失败之后重试次数和时间
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.119.100:2181,192.168.119.110:2181,192.168.119.120:2181")
                .sessionTimeoutMs(200000)
                .connectionTimeoutMs(20000)
                .retryPolicy(retry)
                .build();

        //客户端启动
        client.start();
        System.out.println("zookeeper 客户端启动成功");
        //返回客户端
        return client;

    }
}

[3] 测试结果

在这里插入图片描述

6 企业面试真题

在这里插入图片描述

在这里插入图片描述

后记

本文知识完成了简单的操作部分,源码原理等待后续编写。

posted @ 2022-10-26 18:01  hxld  阅读(69)  评论(0编辑  收藏  举报