zookeeper安装、集群搭建、客户端使用

简单介绍

zookeeper是Apache Hadoop项目下的一个子项目,是一个树形目录服务。中文翻译为动物管理员,用来管理Hadoop(大象),Hive(蜜蜂),pig(小猪)的管理员,简称zk,它是一个分布式的、开源的分布式应用程序的协调服务。

它主要提供三个功能:

1、配置管理
有多个服务都需要连接数据库,每一个服务连接数据库的地址,用户名、密码都是一样的,这时候可以抽出来一个配置中心,将用户名,密码,服务器地址放在配置中心里面,zookeeper可以作为这个配置中心

2、分布式锁
当程序A访问数据的时候,为了避免其他程序的影响,可以加一把锁,如果程序B和A不是在一个电脑上,这时程序B要想访问数据,A的锁不能阻止B来访问数据,为了避免这种现象的发生,可以使用分布式锁,意思是A和其他程序都去“分布式锁”那里去要锁,如果A访问数据拿到了锁,B在访问数据时去“分布式锁“那里要锁,则锁会没有。zookeeper可以充当分布式锁的作用。

3、集群管理
即服务间调用。和nacos一样

zookeeper的样子:
在这里插入图片描述

单体安装

1、安装好zookeeper之后进行解压,目录自己定义即可

2、在安装好的目录下面使用mkdir命令建两个文件夹,data和logs
在这里插入图片描述

3、在conf目录下面找到配置文件zoo.cfg,然后复制一份为zoo1.cfg,命令为cp zoo.cfg zoo1.cfg
在这里插入图片描述

4、复制好之后修改该配置文件,将data和logs加入到该文件内,输入命令: vi zoo1.cfg之后添加如下:
在这里插入图片描述

5、在data目录下面创建 myid 文件使用命令 echo “1” > myid

6、配置环境变量,可以在任意目录启动zk,也可以不配。找到.bash_profile文件,添加zookeeper配置文件,(在用户目录下面,cd~),输入命令 :vi .bash_profile
在这里插入图片描述

7、使配置文件生效,输入命令: source .bash_profile

8、关闭防火墙:
输入命令:sudo systemctl stop firewalld.service

9、启动并测试:
启动命令: zkServer.sh start
查看zk状态: zkServer.sh status

10、关闭zookeeper
输入命令:zkServer.sh stop

集群搭建

搭建方式和单体搭建是一样的,需要安装三次,但是端口不一样即可,这里搭建的是伪集群。
1、设置zk的myid

2881端口的zk,myid为1
2882端口的zk,myid为2
2883端口的zk,myid为3

2、设置每一个zk的配置文件

在每一个zoo.cfg最下面加上同样的代码
server.1=192.168.162.128:2881:3881
server.2=192.168.162.128:2882:3882
server.3=192.168.162.128:2883:3883
在这里插入图片描述

如果是真正的集群的话,则除了服务器地址是不一样的,其他全部都是一样的

3、启动

进入到bin目录下面如果启动命令为 ./zkServer.sh start, 但是报权限不够的错误,则需要提权,
输入命令:chmod a+xwr zkServer.sh 即可。

zk的选举机制

集群搭建的时候需要选取一个zk作为leader,选取leader的方法如下:
leader是5个集群中(假设该集群有5台服务器)的领导者,它说了算。假设一个集群一共有5个服务器zk,每一个zk有自己的id,id编号越大表示在选择算法中的权重就越大。在leader选举的过程中,如果某台zk获得了超过半数的选票,则此台zk可以成为leader了。

假设该集群一共有5个zk,1起来之后投了自己一票,没有过半(才5分之1),不能成为leader,2起来之后 1和2都投了2一票,也没有过半,同理;3起来之后,三个都投了3一票,则3成为leader,之后就不用再继续投票了。

在这里插入图片描述

事务请求表示增删改的操作,非事务请求表示查询的操作。
调度的意思就是给各个sever发通知进行同步节点的数据。
observer和follower唯一的区别就是不具有投票的功能,它的存在意义是为了给followe分担压力

遇到的问题

集群启动的时候报如下错误:

ZooKeeper JMX enabled by defaultUsing config: /usr/soft/zookeeper/apache-zookeeper-3.5.8-bin/bin/…/conf/zoo.cfgStarting zookeeper … already running as process 7145.

解决方法:之前搭建的单体的时候配置了环境变量,所以需要将 用户目录下面的 .bash_profile 中配置的zk目录删除掉
然后进入新建zk的bin文件夹下面,输入命令chmod a+xwr zkServer.sh 进行提权,然后启动即可: ./zkServer.sh start

常见命令

在这里插入图片描述

启动zookeeper:
cd到bin目录下面 输入命令:zkServer.sh start

查看zookeeper状态:
zkServer.sh status

关闭zookeeper:
zkServer.sh stop

注意启动客户端之前要先启动服务端,并且要关闭防火墙。

1、当启动完客户端之后输入Ls / 命令可以查看子节点
在这里插入图片描述

2、创建节点的方式:
create /节点的名字 节点的数据
在这里插入图片描述
这里也可以不写节点的数据,后面再进行set写数据也可以。

3、获取子节点的数据方式:
get /子节点的名字
在这里插入图片描述

4、给子节点设置数据:
Set /子节点的名字 数据
在这里插入图片描述

5、删除子节点的命令:
Delete /子节点的名字
在这里插入图片描述

6、删除全部子节点,如果一个节点下面有多个字节点,全部删除的命令:
deleteall /节点

7、如果命令记不清了,可以输入help来查询命令

创建持久化节点命令:
create -s /app1

创建临时节点命令:
Create -es /app2

查看节点详细信息命令:
Ls -s /app3

Curator使用

curator就是zk的java客户端

1、节点的创建、查询、修改、删除

public class CuratorTest {

    private CuratorFramework curatorFramework;

    /**
     * 建立连接
     */
    @Before
    public void testConnect() {

        //连接的策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

        /*
         * Create a new client
         *
         * @param connectString       连接的地址
         * @param sessionTimeoutMs    session超时时间
         * @param connectionTimeoutMs 连接超时时间
         * @param retryPolicy         重试连接的策略
         * @return client
         */
        //第一种方式
        //curatorFramework = CuratorFrameworkFactory.newClient("192.168.162.128:2181", 60 * 1000, 15 * 1000, retryPolicy);
        开启连接
        //curatorFramework.start();

        //------------------------------------------------------------------------------------------------------------------------------
        //第二种方式;可以添加默认的命名空间,以后节点都会放到该命名空间的节点下面
        curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.162.128:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy).namespace("ming").build();
        //开启连接
        curatorFramework.start();

    }


    /**
     * @Description 测试创建
     * @Date 2021/6/5 16:41
     * @Param []
     * @Return void
     */
    @Test
    public void testCreate() throws Exception {
        //1、基本创建
        //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据进行存储
        String s = curatorFramework.create().forPath("/app1");
        System.out.println(s);
    }

    @Test
    public void testCreate2() throws Exception {
        //1、创建节点,带有数据的
        //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据进行存储
        String s = curatorFramework.create().forPath("/app2", "hahaha".getBytes());
        System.out.println(s);
    }

    @Test
    public void testCreate3() throws Exception {
        //1、设置节点的类型
        //默认的类型是 持久性;下面的意思是当前会话(当前方法)一旦结束,当前创建的节点就会被删除掉。
        String s = curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
        System.out.println(s);

        //执行下面的方法,可以让方法不结束
        while (true) {

        }
    }

    @Test
    public void testCreate4() throws Exception {
        //1、创建多级节点   /app4/p1
        //creatingParentContainersIfNeeded:意思是如果当前节点需要父节点的话,可以进行创建父节点
        String s = curatorFramework.create().creatingParentContainersIfNeeded().forPath("/app4/p1");
        System.out.println(s);
    }


    //==========================================查询数据=======================================

    /**
     * @Description 查询数据
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void testGet1() throws Exception {
        byte[] bytes = curatorFramework.getData().forPath("/app1");
        System.out.println(new String(bytes));
    }


    /**
     * @Description 查询子节点
     * @author Liu Yiming
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void testGet2() throws Exception {
        List<String> list = curatorFramework.getChildren().forPath("/app4");
        for (String s : list) {
            System.out.println(s);
        }
    }


    /**
     * @Description 获取节点状态信息
     * 等同于 客户端中的  ls -s /
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void testGet3() throws Exception {
        //将状态信息放到 Stat这个容器里面。
        Stat stat = new Stat();
        curatorFramework.getData().storingStatIn(stat).forPath("/app1");
        System.out.println(stat);
    }


    //======修改操作====================================================================================

    /**
     * @Description 基本的修改操作
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void set() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        curatorFramework.setData().forPath("/app1", "hahahahah".getBytes());
    }

    /**
     * @Description 根据版本来更新数据
     * 多个javaApi来对同一个客户端进行操作的话,可能会覆盖数据,所以可以指定版本来进行修改
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void setWithVersion() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        Stat status = new Stat();
        curatorFramework.getData().storingStatIn(status).forPath("/app1");
        int version = status.getVersion();
        curatorFramework.setData().withVersion(version).forPath("/app1", "lalalalala".getBytes());
    }


    //====删除操作======================================================================================================

    /**
     * @Description 基本的删除单个节点的操作
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void deleteTest() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        curatorFramework.delete().forPath("/app1");
    }

    /**
     * @Description 删除该节点以及该节点下面的子节点
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void deleteTest2() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        curatorFramework.delete().deletingChildrenIfNeeded().forPath("/app4");
    }

    /**
     * @Description 强制删除,保证一定会删除,本质就是多发送了几次删除请求,目的是防止网络问题,出现删除不成功的现象
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void deleteTest3() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        curatorFramework.delete().guaranteed().forPath("/app1");
    }

    /**
     * @Description 带有回调的删除
     * 回调的意思是执行完当前操作,会返回来一定的内容
     * @Date 2021/6/6 9:48
     * @Param []
     * @Return void
     */
    @Test
    public void deleteTest4() throws Exception {
        //这个 app1 是 ming 目录下面的,因为在最上面创建的时候指定了目录为 ming
        curatorFramework.delete().guaranteed().inBackground(new BackgroundCallback() {
            @Override
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println("执行了删除操作");
                System.out.println(event);
            }
        }).forPath("/app1");
    }


    @After
    public void close() {
        if (curatorFramework != null) {
            curatorFramework.close();
        }
    }
}

2、节点的监听器

zk允许用户在指定节点上注册一些watcher(监听器),并且在一些特定事件触发的时候,zk服务端会将事件通知到感兴趣的客户端上去,该机制是zk实现分布式协调服务的重要特性。
zk中引入了watcher机制来实现发布订阅功能,能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化时,会通知所有的订阅者。
如图所示,三个App都在zk下面,其中如果App1改了自己的内容,zk会通知另外两个app,让其也进行更改。
在这里插入图片描述
监听器的使用


public class CuratorWatcherTest {
    private CuratorFramework curatorFramework;

    /**
     * 建立连接
     */
    @Before
    public void testConnect() {

        //连接的策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

        /*
         * Create a new client
         *
         * @param connectString       连接的地址
         * @param sessionTimeoutMs    session超时时间
         * @param connectionTimeoutMs 连接超时时间
         * @param retryPolicy         重试连接的策略
         * @return client
         */
        //第一种方式
        //curatorFramework = CuratorFrameworkFactory.newClient("192.168.162.128:2181", 60 * 1000, 15 * 1000, retryPolicy);
        开启连接
        //curatorFramework.start();

        //------------------------------------------------------------------------------------------------------------------------------
        //第二种方式;可以添加默认的命名空间,以后节点都会放到该命名空间的节点下面
        curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.162.128:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy).namespace("ming").build();
        //开启连接
        curatorFramework.start();

    }


    //======================================监听器=========================================================

    /**
     * 给指定节点注册监听器
     *
     * @throws Exception
     */
    @Test
    public void testNodeCache() throws Exception {
        //1、创建NodeCache对象
        NodeCache nodeCache = new NodeCache(curatorFramework, "/app1");
        //2、注册监听
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("节点发生了变化~");
                //获取节点的操作内容
                byte[] data = nodeCache.getCurrentData().getData();
                System.out.println(new String(data));
            }
        });
        //3、开启监听,如果设置为true,则开始监听的时候,加载缓冲数据
        nodeCache.start(true);
        //保证该方法可以一直执行
        while (true) {

        }
    }

    /**
     * 监听某个节点的子节点
     *
     * @throws Exception
     */
    @Test
    public void testNodeCache2() throws Exception {
        //1、创建NodeCache对象
        PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework, "/app2", true);
        //2、绑定监听器
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                System.out.println("子节点发生了变化!!");
                System.out.println(pathChildrenCacheEvent);
                //1、获取类型
                PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();
                //2、判断类型是否为update
                if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
                    byte[] data = pathChildrenCacheEvent.getData().getData();
                    System.out.println(new String(data));
                }
            }
        });

        //3、开启
        pathChildrenCache.start();
        //保证该方法可以一直执行
        while (true) {

        }
    }

    /**
     * 使用 TreeCache 监听某个节点自己和所有子节点们,可以监控整个树上的所有节点
     *
     * @throws Exception
     */
    @Test
    public void testNodeCache3() throws Exception {
        //1、创建NodeCache对象
        TreeCache treeCache = new TreeCache(curatorFramework,"/app2");
        //2、注册监听
        treeCache.getListenable().addListener(new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
                System.out.println("节点发生了变化");
                System.out.println(treeCacheEvent);
            }
        });
        //3、开启
        treeCache.start();
        //保证该方法可以一直执行
        while (true) {

        }
    }

    @After
    public void close() {
        if (curatorFramework != null) {
            curatorFramework.close();
        }
    }
}

3、分布式锁

单机应用开发的时候,涉及到并发同步的时候,往往采用synchronized或者lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个jvm下面的,这没有问题,但是当我们的应用是分布式集群工作情况下,不属于jvm下的工作环境,跨jvm之间已经无法通过多线程的锁解决同步问题,就需要其他的锁机制,来解决跨机器进程之间的数据同步问题,这个机制就是分布式锁。
在这里插入图片描述

zk分布式锁原理
核心思想:当客户端想要获取锁,则创建节点,使用完锁之后,删除该节点
1、客户端获取锁时,在lock节点下创建临时顺序节点
为什么要临时:
因为如果一个客户端想要使用该锁(该锁只有一把),是用完之后应该删除掉,给其他客户端来进行使用,这个时候如果该客户端突然宕机了,如果不是临时节点,则节点不会消失,锁也不会释放,其他客户端就会处于等待的状态。而临时锁的意思是如果当前会话消失则锁会被释放。
为什么要顺序:
2、获取lock下面的所有子节点,如果发现自己创建的子节点是所有子节点中最小的一个,则获取该锁,使用完成之后,释放该锁,并删除该节点。
3、如果发现自己创建的节点并非是最小的,则不会获取锁,然后找到离自己最近并且小的节点,对其进行监听,监听删除的事件
4、如果发现比自己小的那个节点被删除了,客户端会收到通知,然后判断自己创建的节点是否是所有子节点中最小的一个,如果是,则获取该锁。

模拟12306卖票的场所

public class Ticket12306 implements Runnable {

    private int ticket = 100;

    //分布式锁
    private InterProcessMutex lock;


    //使用构造方法来创建锁
    //该类被调用的时候,会在zk里面建立一个lock的目录
    public Ticket12306( ) {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.162.128:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy).namespace("ming").build();
        //开启连接
        curatorFramework.start();
        lock = new InterProcessMutex(curatorFramework,"/lock");

    }

    @Override
    public void run() {
        while (true) {
            //获取锁
            try {
                //如果获取不到,3s后在执行
                lock.acquire(3, TimeUnit.SECONDS);
                if (ticket > 0) {
                    System.out.println(Thread.currentThread() + ":" + ticket);
                    ticket--;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放锁
                //无论如何都要将锁进行释放,所以要用finally
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

模拟买票的场所

public class LockTest {
    public static void main(String[] args) {
        Ticket12306 ticket12306 = new Ticket12306();
        Thread thread1 = new Thread(ticket12306, "携程");
        Thread thread2 = new Thread(ticket12306, "飞猪");
        thread1.start();
        thread2.start();
    }
}

这个时候zk里面会有两个临时顺序节点,节点小的会先获取锁
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值