zookeeper的简介及实例

一、zookeeper简介

      ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。

      目前zookeeper被广泛应用于hadoop生态体系中各种框架的分布式协调,我们也可以利用zookeeper来简化分布式应用开发。

二、Zookeeper的核心功能:

  1. 替客户存取数据
  2. 为客户提供对数据的监听服务

三、Zookeeper可以实现的分布式协调服务包括:

 1、统一名称服务

2、配置管理 

3.分布式锁

4.集群节点状态协调(负载均衡/主从协调)

四、zookeeper中的数据结构

1、zookeeper中为用保管数据时,使用的是树状结构(类似于目录树)

2、zookeeper的“目录”树中,每一个节点都叫做一个znode

  • 每一个znode都有自己的path
  • 每一个znode都有自己携带的一份数据(用户的)
  • 每一个znode都有自己的类型:
  1.    PERSISTENT :永久的(只要客户端不删除,则永远存在)
  2.    PERSISTENT_SEQUENTIAL :永久且有序号的
  3.    EMPEMERAL:短暂的(只要客户端掉线,则会被自动删除)
  4.    EMPEMERAL_SEQUENTIAL:短暂且有序号的

znode维护的数据主要是用于存储协调的数据,如状态、配置、位置等信息,每个节点存储的数据量很小,KB级别,最大不要超过1M。

五、zookeeper集群搭建

1、zookeeper集群组件:

Zookeeper集群中的服务器角色有三种:

leader  server,
follower  server,
observer  server;

注意:leader特殊之处在于它有决定权,具有Request Processor(observer server 与follower server的区别就在于不参与leader选举)

2、部署模式:

单节点

分布式(伪分布式)

3、配置文件:

  (1)、添加一个zoo.cfg配置文件  

  $ZOOKEEPER/conf

  mv zoo_sample.cfg zoo.cfg

  (2)、修改配置文件(zoo.cfg)      

dataDir=/itcast/zookeeper-3.4.5/data

server.1=itcast05:2888:3888

server.2=itcast06:2888:3888

server.3=itcast07:2888:3888

 (3)、在(dataDir=/itcast/zookeeper-3.4.5/data)创建一个myid文件,里面内容是server.N中的N(server.2里面内容为2)      

 echo "1" > myid

  (4)、将配置好的zk拷贝到其他节点      

scp -r /itcast/zookeeper-3.4.5/ itcast06:/itcast/

scp -r /itcast/zookeeper-3.4.5/ itcast07:/itcast/

  (5)、注意:在其他节点上一定要修改myid的内容      

在itcast06应该讲myid的内容改为2 (echo "6" > myid)

在itcast07应该讲myid的内容改为3 (echo "7" > myid)

4.启动集群

    分别启动zk

   ./zkServer.sh start

六、zookeeper演示测试

服务启动

bin/zkServer.sh status  获取节点角色状态

Zookeeper支持一下四字节命令来进行交互,查询状态信息等;可以用telnet/nc来发送命令,如:

echo ruok | nc server01 2181

echo conf | nc server01 2181

服务状态详细信息查看(四字命令):四字命令可以获取更多信息

conf

输出相关服务配置的详细信息。

cons

列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。

dump

列出未经处理的会话和临时节点。

envi

输出关于服务环境的详细信息(区别于 conf 命令)。

reqs

列出未经处理的请求

ruok

测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。

stat

输出关于性能和连接的客户端的列表。

wchs

列出服务器 watch 的详细信息。

wchc

通过 session 列出服务器 watch 的详细信息,它的输出是一个与watch 相关的会话的列表。

wchp

通过路径列出服务器 watch 的详细信息。它输出一个与 session相关的路径。

shell客户端操作

bin/zkCli.sh -server server01 2181

操作命令:       

connect host:port
get path [watch]
ls path [watch]
set path data [version]
rmr path
delquota [-n|-b] path
quit
printwatches on|off
create [-s] [-e] path data acl
stat path [watch]
close
ls2 path [watch]
history
listquota path
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
setquota -n|-b val path

七、ZooKeeper数据模型和层次命名空间

     提供的命名空间与标准的文件系统非常相似。一个名称是由通过斜线分隔开的路径名序列所组成的。ZooKeeper中的每一个节点是都通过路径来识别。如图:

八、ZooKeeper中的数据节点:

    1、 每一个节点称为znode,通过路径来访问;

     2、每一个znode维护着:数据、stat数据结构(ACL、时间戳及版本号);

     3、znode维护的数据主要是用于存储协调的数据,如状态、配置、位置等信息,每个节点存储的数据量很小,KB级别;

     4、znode的数据更新后,版本号等控制信息也会更新(增加);

     5、znode还具有原子性操作的特点:写--全部替换,读--全部;

     6、znode有永久节点和临时节点之分:临时节点指创建它的session一结束,该节点即被zookeeper删除。

九、zk性能:

      Zookeeper 的读写速度非常快(基于内存数据库),并且读的速度要比写的速度更快。

     1、顺序一致性:客户端的更新顺序与它们被发送的顺序相一致。

     2、原子性:更新操作要么成功要么失败,没有第三种结果。

     3、单系统镜像:无论客户端连接到哪一个服务器,客户端将看到相同的 ZooKeeper 视图。

     4、可靠性:一旦一个更新操作被应用,那么在客户端再次更新它之前,它的值将不会改变。这个保证将会产生下面两种结果:

           (1)、如果客户端成功地获得了正确的返回代码,那么说明更新已经成功。如果不能够获得返回代码(由于通信错误、超时等等),那么客户端将不知道更新操作是否生效。

           (2)、当从故障恢复的时候,任何客户端能够看到的执行成功的更新操作将不会被回滚。

     5、实时性:在特定的一段时间内,客户端看到的系统需要被保证是实时的。在此时间段内,任何系统的改变将被客户端看到,或者被客户端侦测到。

注意:给予这些一致性保证, ZooKeeper 更高级功能的设计与实现将会变得非常容易,例如: leader 选举、队列以及可撤销锁等机制的实现。

十、zookeeper-api应用

   基本使用,Zookeeper中znode的增删改查以及监听器的使用方法

public class ZKDemo {

     private ZooKeeper zk = null;

     @Before
     public void init() throws Exception {
         zk = new ZooKeeper("127.0.0.1:2181", 2000, new Watcher() {

              /**
               * 监听事件发生时的回调方法
               */
              @Override
              public void process(WatchedEvent event) {

                   if (event.getType() == EventType.None){
                       return;
                   }
   
                   System.out.println(event.getType());
                   System.out.println(event.getPath());

                   try {
                       zk.getData("/aaa", true, null);
                       zk.getChildren("/aaa", true);
                   } catch (KeeperException | InterruptedException e) {
                       e.printStackTrace();
                   }
              }
         });
     }

     /**
      * 向zookeeper服务集群中注册数据,添加znode
      *
      * @throws InterruptedException
      * @throws KeeperException
      * @throws UnsupportedEncodingException
      */
     @Test
     public void testCreateZnode() throws UnsupportedEncodingException, KeeperException, InterruptedException {

         zk.create("/aaa", "这是我的处女作".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

         // sequential的顺序维护是在一个父节点的范围之内
         zk.create("/aaa/bbb", "草草".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

         zk.create("/aaa/ccc", "花花".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

         // 换一个父节点,序号的递增顺序重新开始
         zk.create("/aab/ddd", "树树".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
         zk.close();
     }

     /**
      * 从zkeeper中删除znode
      *
      * @throws Exception
      */
     @Test
     public void testDeleteZnode() throws Exception {

         // 参数1:要删除的节点的路径 参数2:要删除的节点的版本,-1匹配所有版本
         zk.delete("/aab", -1);
         Stat exists = zk.exists("/aab", false);

         System.out.println(exists);
     }

     @Test
     public void testUpdateZnode() throws Exception {
         byte[] data = zk.getData("/aaa", false, null);

         System.out.println(new String(data, "utf-8"));

         zk.setData("/aaa", "我有一头小毛驴我从来也不骑".getBytes("utf-8"), -1);
         data = zk.getData("/aaa", false, null);

         System.out.println(new String(data, "utf-8"));
     }

     /**
      * 获取子节点信息
      *
      * @throws Exception
      */
     @Test
     public void testGetChildren() throws Exception {
         List<String> children = zk.getChildren("/aaa", false);
         for (String child : children) {
              System.out.println(child);
         }

     }

     /**
      * zk的监听机制: 1、事先定义好监听的回调函数 2、在对znode进行各种访问操作时可以注册监听
      * 3、监听的znode上发生相应事件时,客户端zk会接收到zookeeper集群的事件通知 4、客户端zk根据 
      * 事件调用我们事先定义好的回调函数
      *
      * @throws InterruptedException
      * @throws KeeperException
      *
      */
     @Test
     public void testWatch() throws KeeperException, InterruptedException {
         // 在获取znode数据时注册了监听
         // 监听器是一次性,只要监听到一次事件,就失效了
         // getData监听的事件是数据的更改
         byte[] data = zk.getData("/aaa", true, null);

         // 在做查询子节点操作时注册监听
         // 监听的事件就是监听节点下的子节点变化事件
         List<String> children = zk.getChildren("/aaa", true);

         Thread.sleep(Long.MAX_VALUE);
     }
}

十一、zookeeper应用案例(分布式应用HA||分布式锁)

1、实现分布式应用的主节点HA及客户端动态更新主节点状态

A、客户端实现

public class AppClient {

    private String groupNode = "sgroup";
    private ZooKeeper zk;
    private Stat stat = new Stat();
    private volatile List<String> serverList;

    /**
     * 连接zookeeper
     */
    public void connectZookeeper() throws Exception {

       zk = new ZooKeeper("localhost:4180,localhost:4181,localhost:4182", 5000, new Watcher() {
           public void process(WatchedEvent event) {
              // 如果发生了"/sgroup"节点下的子节点变化事件, 更新server列表, 并重新注册监听
              if (event.getType() == EventType.NodeChildrenChanged && ("/" + groupNode).equals(event.getPath())) {
                  try {
                     updateServerList();
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
              }
           }
       });
       updateServerList();
    }

    /**
     * 更新server列表
     */
    private void updateServerList() throws Exception {

       List<String> newServerList = new ArrayList<String>();

       // 获取并监听groupNode的子节点变化
       // watch参数为true, 表示监听子节点变化事件.
       // 每次都需要重新注册监听, 因为一次注册, 只能监听一次事件, 如果还想继续保持监听, 必须重注册
       List<String> subList = zk.getChildren("/" + groupNode, true);
       for (String subNode : subList) {
           // 获取每个子节点下关联的server地址
           byte[] data = zk.getData("/" + groupNode + "/" + subNode, false, stat);
           newServerList.add(new String(data, "utf-8"));
       }

       // 替换server列表
       serverList = newServerList;

       System.out.println("server list updated: " + serverList);
    }

    /**
     * client的工作逻辑写在这个方法中
     * 此处不做任何处理, 只让client sleep
     */
    public void handle() throws InterruptedException {
       Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
       AppClient ac = new AppClient();
       ac.connectZookeeper();
       ac.handle();
    }
}

B、服务器端实现

public class AppServer {

    private String groupNode = "sgroup";
    private String subNode = "sub";

    /**
     * 连接zookeeper
     * @param address server的地址
     */
    public void connectZookeeper(String address) throws Exception {

       ZooKeeper zk = new ZooKeeper("localhost:4180,localhost:4181,localhost:4182",5000, new Watcher() {

           public void process(WatchedEvent event) {
              // 不做处理
           }
       });

       // 在"/sgroup"下创建子节点
       // 子节点的类型设置为EPHEMERAL_SEQUENTIAL, 表明这是一个临时节点, 且在子节点的名称后面加上一串数字后缀
       // 将server的地址数据关联到新创建的子节点上
       String createdPath = zk.create("/" + groupNode + "/" + subNode,address.getBytes("utf-8"),
           Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
       System.out.println("create: " + createdPath);
    }

    /**
     * server的工作逻辑写在这个方法中
     * 此处不做任何处理, 只让server sleep
     */
    public void handle() throws InterruptedException {
       Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {

       // 在参数中指定server的地址
       if (args.length == 0) {
           System.err.println("The first argument must be server address");
           System.exit(1);
       }
       AppServer as = new AppServer();
       as.connectZookeeper(args[0]);
       as.handle();
    }
}

2、分布式共享锁的简单实现

客户端A

public class DistributedClient {

    // 超时时间
    private static final int SESSION_TIMEOUT = 5000;
    // zookeeper server列表
    private String hosts = "localhost:4180,localhost:4181,localhost:4182";
    private String groupNode = "locks";
    private String subNode = "sub";
    private ZooKeeper zk;
    // 当前client创建的子节点
    private String thisPath;
    // 当前client等待的子节点
    private String waitPath;
    private CountDownLatch latch = new CountDownLatch(1);

    /**
     * 连接zookeeper
     */
    public void connectZookeeper() throws Exception {

        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {

            public void process(WatchedEvent event) {

                try {
                    // 连接建立时, 打开latch, 唤醒wait在该latch上的线程
                    if (event.getState() == KeeperState.SyncConnected) {
                        latch.countDown();
                    }
                    // 发生了waitPath的删除事件
                    if (event.getType() == EventType.NodeDeleted && event.getPath().equals(waitPath)) {
                        doSomething();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        // 等待连接建立
        latch.await();

        // 创建子节点
        thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
        // wait一小会, 让结果更清晰一些
        Thread.sleep(10);

        // 注意, 没有必要监听"/locks"的子节点的变化情况
        List<String> childrenNodes = zk.getChildren("/" + groupNode, false);

        // 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁
        if (childrenNodes.size() == 1) {
            doSomething();
        } else {
            String thisNode = thisPath.substring(("/" + groupNode + "/").length());
            // 排序
            Collections.sort(childrenNodes);
            int index = childrenNodes.indexOf(thisNode);
            if (index == -1) {
                // never happened
            } else if (index == 0) {
                // inddx == 0, 说明thisNode在列表中最小, 当前client获得锁
                doSomething();
            } else {
                // 获得排名比thisPath前1位的节点
                this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index - 1);

                // 在waitPath上注册监听器, 当waitPath被删除时, zookeeper会回调监听器的process方法
                zk.getData(waitPath, true, new Stat());
            }
        }
    }

    private void doSomething() throws Exception {

        try {
            System.out.println("gain lock: " + thisPath);
            Thread.sleep(2000);
            // do something
        } finally {
            System.out.println("finished: " + thisPath);
            // 将thisPath删除, 监听thisPath的client将获得通知
            // 相当于释放锁
            zk.delete(this.thisPath, -1);
        }
    }

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    try {
                        DistributedClient dl = new DistributedClient();
                        dl.connectZookeeper();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }

        Thread.sleep(Long.MAX_VALUE);
    }
}

客户端B

public class DistributedClient2 {

    // 超时时间
    private static final int SESSION_TIMEOUT = 5000;
    // zookeeper server列表
    private String hosts = "localhost:4180,localhost:4181,localhost:4182";
    private String groupNode = "locks";
    private String subNode = "sub";
    private ZooKeeper zk;
    // 当前client创建的子节点
    private volatile String thisPath;
    private CountDownLatch latch = new CountDownLatch(1);

    /**
     * 连接zookeeper
     */
    public void connectZookeeper() throws Exception {

       zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {

           public void process(WatchedEvent event) {

              try {
                  // 连接建立时, 打开latch, 唤醒wait在该latch上的线程
                  if (event.getState() == KeeperState.SyncConnected) {
                     latch.countDown();
                  }

                  // 子节点发生变化
                  if (event.getType() == EventType.NodeChildrenChanged && event.getPath().equals("/" + groupNode)) {

                     // thisPath是否是列表中的最小节点
                     List<String> childrenNodes = zk.getChildren("/" + groupNode, true);
                     String thisNode = thisPath.substring(("/" + groupNode + "/").length());
                     // 排序
                     Collections.sort(childrenNodes);
                     if (childrenNodes.indexOf(thisNode) == 0) {
                         doSomething();
                     }
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
           }
       });

       // 等待连接建立
       latch.await();

       // 创建子节点
       thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
       // wait一小会, 让结果更清晰一些
       Thread.sleep(10);

       // 监听子节点的变化
       List<String> childrenNodes = zk.getChildren("/" + groupNode, true);

       // 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁
       if (childrenNodes.size() == 1) {
           doSomething();
       }
    }

    /**
     * 共享资源的访问逻辑写在这个方法中
     */
    private void doSomething() throws Exception {
       try {
           System.out.println("gain lock: " + thisPath);
           Thread.sleep(2000);
           // do something
       } finally {
           System.out.println("finished: " + thisPath);
           // 将thisPath删除, 监听thisPath的client将获得通知
           // 相当于释放锁
           zk.delete(this.thisPath, -1);
       }
    }

    public static void main(String[] args) throws Exception {

       for (int i = 0; i < 10; i++) {
           new Thread() {
              public void run() {
                  try {
                     DistributedClient2 dl = new DistributedClient2();
                     dl.connectZookeeper();
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
              }
           }.start();
       }

       Thread.sleep(Long.MAX_VALUE);
    }
}



 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值