Zookeeper使用详解

1、Zookeeper是什么?用来干什么?

Zookeeper中文动物管理员,Zookeeper是java语言开发的,它主要用在分布式系统架构中。官方文档上这么解释Zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。我们在分布式系统架构中主要用来实现如下功能:

  1. 配制中心;
  2. 分布式锁;
  3. 服务注册;
  4. 发布订阅(消息中间件);
  5. Master选举;
  6. 分布式队列;

以上6功能,后续会讲解,当然Zookeeper,还能实现很多其它功能,当明白其原理之后,可以按需使用它。首先我们一起来看看Zookeeper的架构:

1、文件系统结构:

Zookeeper维护一个类似文件系统的数据结构,如图:

此图,是用Zookeeper客户端工具(ZooInspector)连结Zookeeper服务器显示的效果,图中文件夹和文件称之为节点(znode),可以理解为linux的目录结构,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。Zookeeper中将znode分为4类:

1、PERSISTENT-持久节点

     客户端与Zookeeper服务器断掉连接后,该节点依旧存在,节点被持久化到磁盘。

2、PERSISTENT_SEQUENTIAL-持久顺序编号节点

     客户端与Zookeeper断掉连接后,该节点依旧存在,被持久化到磁盘,创建节点时会自动加序值创建,如图:
           

3、EPHEMERAL-临时节点

      客户端与Zookeeper断掉连接后,该节点被删除。

4、EPHEMERAL_SEQUENTIAL-临时顺序编号节点

      客户端与Zookeeper断掉连接后,该节点被删除,创建节点会自动加序值创建。

2、事件监听机制:

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

2、安装Zookeeper:

首先我们得安装Zookeeper,这里介绍Linux系统上的安装,当然可以用Docker。

Step1:配置JAVA环境,检验环境:java -version。

Step2:下载并解压zookeeper。

   cd /usr/local

   wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz

   tar -zxvf zookeeper-3.4.12.tar.gz

   cd zookeeper-3.4.12

Step3:重命名配置文件zoo_sample.cfg。
   cp conf/zoo_sample.cfg conf/zoo.cfg

Step4:启动zookeeper。

   bin/zkServer.sh start

Step5:检测是否成功启动,用zookeeper客户端连接下服务端。

   bin/zkCli.sh

zkCli.sh是Zookeeper自带的客户端,有如下常用命令:

1、使用 ls 命令来查看当前 ZooKeeper 中所包含的内容:

2、创建一个新的 znode ,使用 create /test tdata:

3、下面我们运行 get 命令来确认第二步中所创建的 znode 是否包含我们所创建的字符串:

zkCli.sh使用不方便也不直观,可使用ZooInspector这个客户端管理工具,工具使用很简单,网上找找就行。

安装好Zookeeper后我们就可以开始学习。

3、Zookeeper相关知识学习与使用:

其实Zookeeper的知识很简单,无非就是4类结点,和事件通知,所谓事件通知,就是客户端连上Zookeeper服务后,当节点新增、删除、或节点中值发生变化,会事件通知客户端,基于这一特性可以实现发布订阅、配制中心等。Zookeeper中的节点不能重名,类似于文件系统的文件名,基于这一特性可以实现分布式服务器的master选主。基于事件通知和节点不能重名可实现分布式锁。Zookeeper中节点可以有序,基于这一特性可以实现分布式先进先出队列。另外Zookeeper中出于安全考虑可以设置访问节点的权限。下面将用代码带着大家走一遍。

1、配制中心:

服务器配制中心用来解决服务器在线参数配制,不像传统文件参数配制需要修改配制文件中的值,重启服务器加载到内存。配制中心用到了Zookeeper事件通知特性。

1、首先创建一个Springboot的简单工程项目,并引入Zookeeper原生的Maven依赖,后续会使用第三方客户端。

<dependencies>

             <dependency>

                    <groupId>org.apache.zookeeper</groupId>

                    <artifactId>zookeeper</artifactId>

                    <version>3.4.12</version>

             </dependency>

</dependencies>

2、通过在服务器上的Zookeeper客户端工具创建一个持久节点/configtest,并设置值test1。

3、在项目中编写如下代码并运行:

public class Application implements Watcher {

    private static ZooKeeper zk = null;
    private static Stat stat = new Stat();
    private static CountDownLatch connectedSemaphore = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
        // zookeeper配置数据存放路径
        String path = "/configtest";

        // 连接zookeeper并且注册一个默认的监听器,ZooKeeper实现了Watcher监听
        zk = new ZooKeeper("201.37.83.45:2181", 50, new Application());

        // 等待zk连接成功的通知
        connectedSemaphore.await();

        // 获取path目录节点的配置数据
        System.out.println(new String(zk.getData(path, true, stat)));

        Thread.sleep(Integer.MAX_VALUE);
    }

    public void process(WatchedEvent event) {

        // zk连接成功通知事件
        if (KeeperState.SyncConnected == event.getState()) {
            if (EventType.None == event.getType() && null == event.getPath()) {
                connectedSemaphore.countDown();
                System.out.println("connect server!");
            } else if (event.getType() == EventType.NodeDataChanged) {
                // zk目录节点数据变化通知事件
                try {
                    System.out.println("配置已修改,新值为:" + new String(zk.getData(event.getPath(), true, stat)));
                } catch (Exception e) {
                }
            }
        }

        // zk断掉连结通知事件
        if (KeeperState.Disconnected == event.getState()) {
            System.out.println("dissconnect server!");
        }
    }
}
运行程序,连结Zookeeper服务器成功后,会获取节点的值,如图:

然后通过ZooInspector节点的值,如图:

点击“是”,程序收到事件通知,获取新值如图:

2、节点权限:

Zookeeper节点的数据结构非常类似于Linux文件系统的结构。在Linux文件系统中,每个文件或目录针对不同的用户和用户组都具有相应的rwx权限。同样的,在Zookeeper中,每个节点针对不同的用户或主机也具有相应的权限。下面我们来看下Zookeeper中节点权限的基本概念。

在Zookeeper中,权限模式有两种类型,分别是ip和digest。

1、ip模式是基于ip白名单的方式指定某个服务器具有那些权限;

2、digest模式是基于用户名和密码的方式指定谁具有那些权限;

在ip权限模式下,ID就是具体的ip地址字符串,在digest权限模式下,ID是username:Base64(Sha1(username:password))字符串。在Zookeeper中,有create(c)、delete(d)、read(r)、write(w)和admin(a)这五种权限类型。

下面用代码示例digest权限。

1、首先创建一个Springboot的简单工程项目,并引入Zookeeper第三方客户端的Maven依赖。
<dependencies>
    <dependency>
        <groupId>com.101tec</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.10</version>
    </dependency>
</dependencies>
2、在项目中编写如下代码并运行:
public class Application {

    // zk连接地址
    private static final String address = "201.37.83.45:2181";

    // zk连接客户端
    protected static ZkClient zkClient = new ZkClient(address);

    public static void main(String[] args) {

        try {
            // 构造权限信息
            List<ACL> acl = new ArrayList<ACL>();
            // 指定用户名和密码的权限
            acl.add(new ACL(ZooDefs.Perms.ALL, /* 指定所有权限类型 */
                    new Id("digest", DigestAuthenticationProvider.generateDigest("admin:admin123"))));

            // 创建节点
            zkClient.create("/idacl", "helloworld!", acl, CreateMode.EPHEMERAL);

            // 未添加授权信息,读取失败
            try {
                System.out.println(zkClient.readData("/idacl"));
            } catch (Exception e) {
                System.out.println("权限不够");
            }

            // 添加授权信息
            zkClient.addAuthInfo("digest", "admin:admin123".getBytes());

            try {
                System.out.println(zkClient.readData("/idacl"));
            } catch (Exception e) {
                System.out.println("权限不够");
                return;
            }

            System.out.println("有权限读取");

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return;
    }
}

运行结果:

3、分布式锁:

为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。如集群服务器中跑批处理,某一时刻仅需一个服务器工作。

下面用代码示例Zookeeper实现分布式锁,主要用到了Zookeeper的节点不能同名、事件通知(节点删除的事件通知)和临时节点连结断掉就自动删除的特性。

1、首先创建一个Springboot的简单工程项目,并引入Zookeeper第三方客户端的Maven依赖:

<dependencies>

       <dependency>

              <groupId>com.101tec</groupId>

              <artifactId>zkclient</artifactId>

              <version>0.10</version>

       </dependency>

</dependencies>

2、在项目中编写如下代码并运行:

第一个Class,锁接口的定义:
/*
 * 分布式锁的接口定义
 */
public interface IDistributedLock {
    void lock();

    void unLock();
}

第二个Class,用Zookeeper实现的分布式锁:
/*
 * 用Zookeeper实现分布式锁
 */
public class ZooKeeperDistributedLock implements IDistributedLock {

    // zk连接地址
    private static final String address = "201.37.83.45:2181";

    // zk临时节点
    protected static final String path = "/lock";

    // zk连接客户端
    protected ZkClient zkClient = new ZkClient(address);

    protected CountDownLatch countDownLatch = null;

    public void lock() {

        // 尝试去上锁
        if (tryLock()) {
            System.out.println("###上锁成功###");
            return;
        }

        // 等待去上锁
        waitLock();
        // 再次去上锁
        lock();
    }

    public void unLock() {
        if (null != zkClient) {
            zkClient.close();
        }
    }

    private boolean tryLock() {
        try {
            zkClient.createEphemeral(path);
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    private void waitLock() {

        // 使用事件监听,获取到节点被删除
        IZkDataListener iZkDataListener = new IZkDataListener() {

            // 当节点被删除的时候
            public void handleDataDeleted(String dataPath) throws Exception {
                if (null != countDownLatch) {
                    // 信号量减一,唤醒
                    countDownLatch.countDown();
                }
            }

            // 当节点发生改变
            public void handleDataChange(String dataPath, Object data) throws Exception {
            }
        };

        // 注册节点监听事件
        zkClient.subscribeDataChanges(path, iZkDataListener);

        // 检测节点是否存在,如果存在则等待
        if (zkClient.exists(path)) {
            // 创建信号量
            countDownLatch = new CountDownLatch(1);

            try {
                // 进行等待
                countDownLatch.await();
            } catch (Exception e) {
            }
        }

        // 删除节点事件通知
        zkClient.unsubscribeDataChanges(path, iZkDataListener);
    }
}

第三个Class,线程共享资源的实现,用以模拟多进程:
public class TestRunnable implements Runnable {

    private boolean bIfLock;
    private CountDownLatch countDownLatch;
    private IDistributedLock distributedLock;

    private static int showTimes;

    /*
     * 该类实现线程接口,测试时多个线程,每次加showTimes+1打印值,然后再++showTimes;
     * 模拟多服务器多进程共享资源使用,如果不加锁将会出现大量重复的值(如果是火车票,多人将会拿到同一张票)。
     */

    public TestRunnable(boolean bIfLock, CountDownLatch countDownLatch) {

        this.bIfLock = bIfLock;
        this.countDownLatch = countDownLatch;

        if (bIfLock) {
            distributedLock = new ZooKeeperDistributedLock();
        }
    }

    public void run() {

        String threadName = Thread.currentThread().getName();

        for (int i = 0; i < 10; i++) {

            if (bIfLock) {
                distributedLock = new ZooKeeperDistributedLock();
                distributedLock.lock();
            }

            /* 不使用分布式事务,很容易出现重复 */
            System.out.println(threadName + (TestRunnable.showTimes + 1));
            ++TestRunnable.showTimes;

            if (null != distributedLock) {
                distributedLock.unLock();
                distributedLock = null;
            }
        }

        countDownLatch.countDown();
    }
}

第四个Class,主程序调用:
/*
 * 主程序
 */
public class Application {

    public static void main(String[] args) {

        int iTimes = 3;
        CountDownLatch countDownLatch = new CountDownLatch(iTimes);

        Thread thread = null;
        TestRunnable runnable = null;

        for (int i = 0; i < iTimes; i++) {
            runnable = new TestRunnable(true/* false不开启分布式事务 */, countDownLatch);
            thread = new Thread(runnable, "the " + i + " Tread ");
            thread.start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return;
    }
}

不使用锁的运结果:

使用锁的运结果:

4、分布式队列:

分布式队列简单理解就是实现跨进程、跨主机、跨网络的数据共享与数据传递,借助Zookeeper可以处理两种类型的分布式队列:

1、同步队列:

当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

如:一个公司去旅游,看是否所有人都到齐了,到齐了才发车;

如:一个大任务分解为多个子任务,需所有子任务都完成了才能进入到下一流程;

可在Zookeeper中先创建一个根目录/sync_queue,做为队列,队列的消费者监视/sync_queue节点,入队列操作就是在/sync_queue下创建子节点,然后计算子节点的总数,看是否和队列的目标数量相同,由于/sync_queue这个节点有了状态变化,Zookeeper就会通知监视者,监视者得到通知后进行目标数量相等判断。

2、先进先出队列:

按照FIFO方式进行入队和出队,在Zookeeper中先创建一个根目录/fifo_queue,做为队列,入队列操作就是在/fifo_queue下创建自增序的子节点,并把数据放入节点内,出队列操作就是先找到/fifo_queue下序号最小的那个节点,取出数据,然后删除此节点。

下面用代码示例Zookeeper的有序节点。

1、首先创建一个Springboot的简单工程项目,并引入Zookeeper第三方客户端的Maven依赖。

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

2、在项目中编写如下代码并运行:

public class Application {

    // zk连接地址
    private static final String address = "201.37.83.45:2181";

    // zk连接客户端
    protected static ZkClient zkClient = new ZkClient(address);

    public static void main(String[] args) {

        // 判断持久节点是否存在,存在无需再创建,zookeeper不允许有同名结点。
        if (!zkClient.exists("/sroot")) {
            zkClient.createPersistent("/sroot"); // 创建持久结点
            // zkClient.createEphemeral("/sroot"); // 创建临时结点,临时结点不能做为父结点。
        }

        /*
         * 有序节点就是创建时,会自动在节点后面加一个序号,基于这特性,对于节点创建时,可以使用同名节点名, 因为真正创建时,会自动加上序号。
         */
        for (int i = 0; i < 10; i++) {
            zkClient.createEphemeralSequential("/sroot/sequential", i + ""); // 创建有序临时结点
        }

        /*
         * 有序节点读值,可以用父子节点读法获取。
         */
        List<String> children = zkClient.getChildren("/sroot");

        for (int i = 0; i < children.size(); i++) {
            System.out.println(zkClient.readData("/sroot/" + children.get(i)));
        }

        System.out.println("run over");

        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return;
    }
}

运行结果如图:

以上所述,不是Zookeeper的所有用法,仅是一些常用的用法,其它用法,请感兴趣的朋友自己去了解,另外以上所写,可能有一些问题,望留言纠正,或请加qq(907128466)群一起学习讨论,谢谢每位观看到朋友!

下一篇文章将会继续述Zookeeper集群相关知识。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值