Zookeeper集群

Zookeeper简介

Apache Zookeeper:把Zookeeper拆开来读,zoo-keeper,即动物园管理员。致力于开发和维护开源服务器,实现高度可靠的分布式协调。

什么是Zookeeper?

Zookeeper是一种集中式服务,用于配置维护,域名服务,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式。每次使用这些服务时,都需要做大量重复的操作。即使正确完成这些操作,这些服务的不同实现也会在部署应用程序时导致管理复杂性。而Zookeeper的目标就是封装好复制易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

Zookeeper的功能

主要功能有两个:
1)存储数据
2)监听

存储数据,存储的是Zookeeper的节点。
监听,监听Zookeeper的数据变化。

在Zookeeper中,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据。Zookeeper使用Wathcer监听事件信息,当客户端接收到事件信息,比如连接超时、节点数据改变、子节占改变,可以调用相应的行为来处理数据。

Zookeeper目录树结构:
在这里插入图片描述
根目录状态信息:
在这里插入图片描述

Zookeeper工作机制

zookeeper是基于观察者模式设计的分布式服务管理框架。如下图所示:

在这里插入图片描述

  • 服务器在启动时会在zk集群创建一个临时节点,临时节点在断开连接时会被删除。
  • “监听”客户端会获取指定路径下的节点信息,当节点信息发生改变时,会收到通知。
  • 上图中是一个观察者设计模式的实现,“监听”客户端扮演具体观察者角色,而服务器扮演具体被观察者角色。

Zookeeper应用场景

  1. 集群统一配置管理
  2. 集群统一命名服务
  3. 注册中心
  4. 服务器的动态上下线感知
  5. 负载均衡
  6. 分布式锁

Zookeeper安装

  1. 下载安装包,zookeeper-3.4.10
    链接: https://pan.baidu.com/s/1PuLqMdVBu6T9nqIUBcAURg 提取码: wwpr
  2. 上传安装包到linux
  3. 解压安装包
    tar -zxvf zookeeper-3.4.10.tar.gz
  4. 把 zookeeper-3.4.10/conf/zoo_sample.cfg重命名为zoo.cfg,并指定zNode数据存放的目录:
    mv zoo_sample.cfg zoo.cfg
    vi zoo.cfg
    在这里插入图片描述
    dataDir=/home/even/hd/zookeeper-3.4.10/zkData
    在这里插入图片描述
  5. 启动zk
    bin/zkServer.sh start
    在这里插入图片描述
  6. jsp查看启动
    在这里插入图片描述
  7. 查看状态
    bin/zkServer.sh status
    在这里插入图片描述
    其中Mode:standalone代表是单机模式
  8. 启动客户端
    bin/zkCli.sh
    在这里插入图片描述
  9. 查看根目录状态
    stat /
    在这里插入图片描述

zookeeper分布式安装

  1. 把安装包拷贝到需要搭建集群的机器上在集群机器上重复上面的3,4步骤(此处拷贝使用ssh免密操作)。
    scp zookeeper.tar.gz hd-even-02:$PWD

    scp zookeeper.tar.gz hd-even-03:$PWD

  2. 修改配置文件
    vi zoo.cfg
    修改内容:
    dataDir=/home/even/hd/zookeeper-3.4.10/zkData
    server.1=hd-even-01:2888:3888
    server.2=hd-even-02:2888:3888
    server.3=hd-even-03:2888:3888
    在这里插入图片描述

  3. 在zkData目录下添加文件myid
    cd zookeeper-3.4.10/zkData
    touch myid

  4. 添加内容在myid,内容为上面zoo.cfg配置文件的server.{num}中的num,根据不同的机器添加不同的内容。myid的作用是与配置文件进行比较,从而来确定本身属于哪台机器。
    vi myid
    在这里插入图片描述

  5. 修改环境变更
    vi /etc/profile
    export ZOOKEEPER_HOME=/home/even/hd/zookeeper-3.4.10
    export PATH= P A T H : PATH: PATH:ZOOKEEPER_HOME/bin

  6. 使环境变量生效
    source /etc/profile

  7. 启动zk,集群中的机器都需要启动一遍。
    bin/zkServer.sh start

  8. 查看状态
    bin/zkServer.sh status
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    此处,我是先启动hd-even-01,然后再启动hd-even-03,最后启动hd-even-02机器。因此,hd-even-03的zookeeper是leader。

  9. 启动客户端
    bin/zkCli.sh

Zookeeper命令行

  1. 启动客户端:zkCli.sh
  2. 查看帮助:help
  3. 查看当前znode所包含的内容:ls /
  4. 创建节点:create /even 数据
  5. 创建短暂znode:create -e /zoo even
  6. 创建带序号的znode:create -s /zoo1 even
  7. 创建短暂带序号的znode:create -e -s /zoo2 even
  8. 查看此节点的详细信息:ls2 /
  9. 获取节点值监听:get /even watch(可为空)
  10. 监听路径:ls / watch
  11. 修改znode数据:set /even 需要设置的数据
  12. 删除节点:delete /even
  13. 递归删除:rmr /zoo1
  14. 查看节点状态信息:stat /

Zookeeper选举机制

Zookeeper选举是为了选举出Leader角色的ZK服务器。ZK集群主要有两个角色组成:Leader和Follower,另外其实还有一个ObServer观察者角色。

角色描述
领导者(Leader)领导者负责进行投票的发起和决议,更新系统状态
跟随者(Follower)Follow用于接收客户请求并向客户端返回结果,在选举过程中参与投票
观察者(ObServer)ObServer可以接收客户端连接,将写请求转发给Leader节点。但是ObServer不参与投票过程,只同步Leader的状态。目的是为了提高系统读取速度
客户端(Client)向zk集群发起请求

Zookeeper选举机制如下图:
在这里插入图片描述

  • 当集群没有选举出Leader时,此时zk服务器会进行LOOKING竞选状态,当竞选出Leader后,根据自身Mode,分为FOLLOWING(随从状态),OBSERVING(观察状态)和LEADING(领导者状态)。
  • 选举算法中用到的参数有:
    1)服务器ID,即myid的内容。编号越大,在选择算法中的权重越大。
    2)数据ID,服务器中存放的最大数据ID,值越大,说明数据越新,在选举算法中权重越大。
    3)逻辑时钟,也叫投票的次数,同一轮投票过程中,逻辑时钟值是相同的,每投完一次票,这个数据会增加,然后与接收到的其他服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。值越大,代表投票越新。
    4)选举状态,即LOOKING,FOLLOWING,OBSERVING,LEADING。
  • 投票过程中,集群中所有的服务器之间传递的信息叫选举消息,内容包含:服务器ID,数据ID,逻辑时钟,选举状态。
  • 集群中的机器必须是奇数台。

为什么ZK集群需要奇数台服务器?

ZK集群选举机制决定了,当票数达到半数以上时,当选Leader,因此,奇数台服务器的好处有两点:

  1. 2n+1和2n+2台机器的容灾能力相同。
    2n+1和2n+2台机器组成的zookeeper集群,都允许最多宕机n台。因此,容灾能力一样的情况下,只需要2n+1台机器就可以了。
  2. 降低集群启动时因脑裂导致集群不可用的问题。
    因网络问题,导致部分节点不可达,使得集群内部出现多个小集群,叫做脑裂。在启动Zookeeper集群进行选举Leader时,如果出现这种情况,那么,2n+1台机器至少能保证集群可用率大于2n+n台机器。
    在这里插入图片描述

(注意:集群运行时出现脑裂(发生原因:Leader位于机器数少的小集群时,机器数多的小集群很有可能会内部再选举一个Leader出来)是不可避免的,只能通过各种方式优化,在此不作详述。)

Zookeeper服务

Zookeeper作为一个高可用性的高性能协调服务,下面从三个方面来了解这个服务:数据模型、操作和实现。

数据模型

Zookeeper维护一个树状结构目录,树中的每一个节点被称为znode,znode用于存储数据,并且有一个与之相关联的ACL。Zookeeper是用于实现协调服务的,这类服务通常使用小数据文件,而不是大容量数据。因此,一个znode默认能存储1MB就够了。

Zookeeper的数据访问具有原子性,即读写数据过程中,要么全部读写成功,要么失败。Zookeeper的写操作是替换znode存储的内容,而不是追加操作。Zookeeper也不支持添加操作。

znode通过路径来引用,类型Unix文件系统路径,但是Zookeeper中的路径必须为绝对路径,其中“zookeeper”是一个保留词。

znode重要性质:

  1. 临时znode
    znode有两种类型:临时(ephemeral)和持久(persistent)。znode的类型在创建时就被指定且后续不可更改。临时znode的客户端会话结束时,Zookeeper会将该临时znode删除;而持久zonde不依赖于客户端会话,即使会话结束,也不会删除。注意:临时znode不可以有子节点。
    znode根据ACL的定义来决定可见性。znode的两种类型需要根据具体的业务逻辑来决定使用。如集群服务器上下线可以使用临时节点,而集群服务器配置统一管理可以使用持久节点。

  2. 顺序号
    除了临时节点和持久节点外,还有一种类型是顺序号节点,顺序号需要和临时或持久节点同时使用。当创建一个带顺序号的节点时,znode名称之后会添加一个值,该值是由一个单调递增的计数器所添加的(由父节点维护)。
    带顺序号的节点一般为所有事件进行全局排序,或者是分布式锁。

  3. 观察
    znode以某种方式发生变化时,watch机制可以让客户端收到通知。可以针对Zookeeper的操作来设置watch,以便实现业务逻辑。
    观察只能触发一次,如果需要多次观察,则需要重复注册所需要的观察。

操作

Zookeeper的9种基本操作:

操作描述
create用于创建znode
delete删除一个znode,不能删除有子节点的znode
exists测试一个znode是否存在并且查询它的元数据stat
getACL,setACL获取/设置一个znode的ACL
getChildren获取一个znode的子节点
getData/setData获取/设置一个znode所保存的数据
sync将客户端的znode视图与Zookeeper同步

其中,delete和setData需要提供znode的版本号(可以通过exists获取)。更新操作是非阻塞操作,更新失败后,可以重试或其他操作。

sync操作的意思:在Zookeeper读写操作时,写操作必须保证将数据写到Zookeeper服务器上,但是客户端从Zookeeper读数据时,可以允许有滞后,因此,客户端使用sync可以获取数据的最新状态。

  • 集合更新
    Zookeeper有一个multi的操作,用于将多个基本操作集合成一个操作单元,并确保这些操作同时被成功执行,或同时失败,类似事务。一般用于无向图的操作。

  • 同步/异步
    Zookeeper执行操作时,可以选择是同步还是异步,形如Stat exists(String path,Watcher watcher)的是同步,而void exists(String path,Watcher watcher,StatCallback cb,Object ctx)的是异步操作。StatCallback是Zookeeper响应时会被调用到的回调实现,该回调方法实现了以下方法:
    public void processResult(int rc, String path,Object ctx,Stat stat)
    其中rc参数是返回代码,对应于KeeperException的代码,非零代码代表一个异常,当发生异常时,stat为null。path和ctx参数对应于客户端调用exists方法时传递的参数,用于识别这个回调所响应的请求。

  • 观察触发器
    watch可以在exists、getChildren和getData这些读操作上设置,这些观察可以被写操作create、delete和setData触发。当观察被触发时,会产生一个观察事件。观察事件的类型由观察和触发它的操作共同决定。

    操作与事件类型的关系表:

观察的操作创建znode创建子节点删除znode删除子节点setData
existsNodeCreatedNodeDeletedNodeDataChanged
getDataNodeDeletedNodeDataChanged
getChildrenNodeChildrenChangedNodeDeletedNodeChildrenChanged
  • ACL列表
    每个znode创建时都会带有一个ACL列表,用于决定谁可以对它进行何种操作。
    Zookeeper客户端身份验证机制方式:
    1)digest 通过用户名和密码来识别客户端
    2)sasl 通过Kerberos来识别客户端
    3)ip 通过客户端IP地址来识别客户端

digest方式:zk.addAuthInfo(“digest”,“name:password”.getBytes())
ip方式:new ACL(Perms.READ,new Id(“ip”,“192.168.0.1”))

在java中,可以通过Perms类来获取权限内容。下面是ACL权限与操作的对照表:

ACL权限允许的操作
CREATEcreate()
READgetChildren/getData
WRITEsetData
DELETEdelete()
ADMINsetACL

其中exists的操作不受权限控制。

  • Zookeeper实现Zab协议,该协议包括两个可以无限重复的阶段:
    一,领导者选举
    集群中所有机器通过一个选举过程来选出一台被称为Leader的机器,其他被称为follower。一旦半数以上的follower已经将leader同步,则表明这个阶段已完成。

二,原子广播
所有写请求都会被转发给leader,再由leader更新广播给follower。当半数以上的follower已经被修改持久化之后,leader才会提交这个更新,然后客户端才会收到已更新的响应。该过程和具有原子性,要么成功,要么失败。

  • 数据一致性
    1)顺序一致性
    多个客户端提交修改时,zookeeper会按发送顺序来更新。每一次更新都被赋予一个全局唯一的ID,称为zxid,zxid最大,代表执行的更新越新。
    2)原子性
    要么成功,要么失败。
    3)单一系统映像
    一个客户端无论连接哪一台机器,所看到的都是相同的系统目录。即如果客户端在一个会话中连接一台服务器,此时该服务器宕机了,客户端重新连接其他服务器,对于状态在宕机服务器之后的服务器是无法连接上的,至少状态更新要和宕机的服务器一致。
    4)持久性
    因为更新回复是在持久化成功后才会发送,因此,当接收到一个更新,则代表此更新已被持久化到服务器,即使服务器故障,也不会影响到持久化的数据。
    5)及时性
    当需要获取Zookeeper服务器上最新状态的值,可以使用sync操作。其中sync操作只能以异步的方式被调用。

Zookeeper会话

zookeeper客户端需要事先指定zookeeper集群服务器的列表,以" ip1:2181,ip2:2181"格式指定,在启动客户端时,会尝试连接到列表中的一台服务器,如果连接失败,它会尝试连接另一台服务器,直到成功与一台服务器建立连接或zookeeper服务器不可用而失败。

当客户端与zookeeper服务器建立连接时,会生成一个新的会话,每个会话都会有一个超时的时间设置,这个设置由创建会话的应用来设定。如果服务器在超时时间段内没有收到任何请求,则会话过程,无法重新打开,并且任何与该会话相关的临时znode都会丢失。

一般情况下,会话不会过期,因为在一个会话空闲超过一定时间后,zookeeper客户端库会自动发送一个ping请求,以此来保持心跳。这个时间可以设置,一般设置足够低,以便能够检测服务器故障,并且,zookeeper客户端可以自动进行故障切换,即当连接的服务器出现故障时,可以自动连接另一台服务器,且所有的会话仍然有效,临时节点也不会消失。

在切换服务器过程中,观察通知将无法发送,但是在客户端成功恢复连接后,没有发送的通知会再次发送,而在切换过程中,应用程序试图执行的操作都会失败。

  • 时间参数:tick time参数是zookeeper中的基本时间周期,集群中的服务器会拿它来定义相互交互的时间表,一些设置也是根据tick time参数来定义的。如会话超时参数的值不可以小于2个tick time,并且不可以大于20个tick time。如果设置的时间在这个范围外,程序会自动修改它到该范围内。一般tick time设置为2秒。

状态

zookeeper对象的生命周期会有几个不同的状态,其中java可以通过getState()方法查询对象的状态。

States代表Zookeeper对象不同状态的枚举类型值,一个Zookeeper的实例在一个时刻只会处于一种状态。当与Zookeeper建立连接的过程中,Zookeeper实例处于CONNECTING状态,一旦建立连接,状态为CONNECTED状态,如果此时注册了一个观察对象,那么观察对象会收到一个WatchEvent通知,其中KeeperState的值是SyncConnected。在此特别说明,zookeeper的watcher可以获得zookeeper状态变化的通知,也可以获得znode变化的通知。

当断开连接时,观察会收到一个由zookeeper实例发起的Disconnected事件,如果连接丢失,它会自动尝试重新连接。

如果调用了zookeeper.close()方法或出现会话超时,Zookeeper实例会转换成CLOSED状态,而观察事件的KeeperState值为Expired。此时的Zookeeper对象不再是活跃的,需要重新创建一个新的Zookeeper实例才能连接Zookeeper服务。
在这里插入图片描述

JAVA API

导入包:

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
            <type>pom</type>
        </dependency>

基本操作

  1. 新建Zookeeper实例:
public class ZookeeperClient {
   public static final int SESSION_TIME_OUT = 3000;
   public ZooKeeper zooKeeper;
   public CountDownLatch countDownLatch = new CountDownLatch(1);
   public static final String ZOO_ROOT_PATH = "/";
   /**
    * 连接zookeeper服务
    *
    * @param hosts zookeeper集群hosts
    * @throws IOException
    * @throws InterruptedException
    */
   public void connect(String hosts) throws IOException, InterruptedException {
       zooKeeper = new ZooKeeper(hosts, SESSION_TIME_OUT, new Watcher() {
           @Override
           public void process(WatchedEvent event) {
               if (event.getState() == Event.KeeperState.SyncConnected)
                   /*递减1,为0,await返回*/
                   countDownLatch.countDown();
           }
       });
       /*等待连接成功*/
       countDownLatch.await();
   }
}
  1. 创建节点:
 /**
     * 创建组
     *
     * @param groupName 组名
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void createGroup(String groupName) throws KeeperException, InterruptedException {
        String path = ZOO_ROOT_PATH + groupName;
        String services = zooKeeper.create(path, "services".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println("创建组:" + services);
    }
  1. 创建临时子节点:
    /**
     * 组成员注册
     *
     * @param groupName  组名
     * @param memberName 成员名
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void join(String groupName, String memberName) throws KeeperException, InterruptedException {
        String path = ZOO_ROOT_PATH + groupName + ZOO_ROOT_PATH + memberName;
        /*创建一个对所有客户端都可见的临时节点*/
        String s = zooKeeper.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        System.out.println("创建成员成功:" + s);
    }
  1. 显示子节点:
    /**
     * 显示组成员
     *
     * @param groupName 组名
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void list(String groupName) throws KeeperException, InterruptedException {
        String path = ZOO_ROOT_PATH + groupName;
        List<String> children = zooKeeper.getChildren(path, null);
        if (children.isEmpty())
            System.out.println("此分组没有成员!!");
        for (String child : children) {
            System.out.println(child);
        }
    }
  1. 删除节点:
    /**
     * 删除组,delete方法不能递归删除,需要先把组内成员删除
     *
     * @param groupName
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void deleteGroup(String groupName) throws KeeperException, InterruptedException {
        String path = ZOO_ROOT_PATH + groupName;
        List<String> children = zooKeeper.getChildren(path, null);
        for (String child : children) {
            zooKeeper.delete(child, -1);
        }
        zooKeeper.delete(path, -1);
    }
  1. 关闭连接:
    /**
     * 关闭连接
     *
     * @throws InterruptedException
     */
    public void close() throws InterruptedException {
        zooKeeper.close();
    }
  1. Main方法:
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        ZookeeperClient zookeeperClient = new ZookeeperClient();
        zookeeperClient.connect("hd-even-01:2181,hd-even-02:2181,hd-even-03:2181");
        /*创建组*/
        zookeeperClient.createGroup("services");
        /*创建组成员*/
        zookeeperClient.join("services", "service1");
        /*显示组成员*/
        zookeeperClient.list("services");
        /*删除组成员*/
        zookeeperClient.deleteGroup("services");
        Thread.sleep(Long.MAX_VALUE);
        zookeeperClient.close();
    }

服务器动态上下线感知

  1. 模拟服务器注册信息,即创建临时子节点,ZKServer.java。
package com.even.zookeeper;

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

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.CountDownLatch;

/**
 * Project Name: Web_App
 * Des: 服务器注册消息
 * Created by Even on 2019/1/7
 */
public class ZKServer {
    public final String HOSTS = "hd-even-01:2181,hd-even-02:2181,hd-even-03:2181";
    public final int SESSION_TIME_OUT = 3000;
    public CountDownLatch countDownLatch = new CountDownLatch(1);
    public ZooKeeper zookeeper;
    public Charset CHARSET = Charset.forName("UTF-8");

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        ZKServer zkServer = new ZKServer();
        zkServer.connect();
        zkServer.createGroup("services");
        zkServer.join("services", "service1");
        Thread.sleep(Long.MAX_VALUE);
    }

    public void connect() throws IOException, InterruptedException {
        zookeeper = new ZooKeeper(HOSTS, SESSION_TIME_OUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (event.getState() == Event.KeeperState.SyncConnected)
                    countDownLatch.countDown();
            }
        });
        countDownLatch.await();
    }

    public void createGroup(String groupName) throws KeeperException, InterruptedException {
        Stat stat = zookeeper.exists("/" + groupName, null);
        if (null == stat) {
            String s = zookeeper.create("/" + groupName, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.printf("Create znode %s success\n", s);
        }
    }
    public void join(String groupName, String memberName) throws KeeperException, InterruptedException {
        /*此时创建一个临时带序号的子节点*/
        String s = zookeeper.create("/" + groupName + "/" + "server", memberName.getBytes(CHARSET), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.printf("server %s  上线了\n", s);
    }
}
  1. 监听服务器列表变化,ZKClient.java:
package com.even.zookeeper;

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

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 * Project Name: Web_App
 * Des: 监听服务器上下线
 * Created by Even on 2019/1/7
 */
public class ZKClient implements Watcher {
    /*重试次数*/
    private final int MAX_RETRIES = 10;
    /*重试时间差*/
    private final int RETRY_TIME_OUT = 30;

    public final String HOSTS = "hd-even-01:2181,hd-even-02:2181,hd-even-03:2181";
    public final int SESSION_TIME_OUT = 3000;
    /*计数器,用于确保zookeeper已连接成功*/
    public CountDownLatch countDownLatch = new CountDownLatch(1);
    public ZooKeeper zookeeper;
    public Charset CHARSET = Charset.forName("UTF-8");
    /*当前服务器列表,当发生上下线时,会更新*/
    Set<String> serverList = new HashSet<>();

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        while (true) {
            ZKClient zkClient = new ZKClient();
            zkClient.connect("services");
            try {
                zkClient.listen("services");
            } catch (KeeperException.SessionExpiredException e) {
                break;
            } catch (KeeperException e) {
                throw e;
            }
        }
    }
    /**
     * 连接zookeeper服务,获取指定znode的子节点,并保存下来
     * @param group
     * @throws IOException
     * @throws InterruptedException
     */
    public void connect(final String group) throws IOException, InterruptedException {
        zookeeper = new ZooKeeper(HOSTS, SESSION_TIME_OUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    countDownLatch.countDown();
                    try {
                        List<String> children = zookeeper.getChildren("/" + group, null);
                        for (String child : children) {
                            byte[] data = zookeeper.getData("/" + group + "/" + child, null, null);
                            serverList.add(new String(data, CHARSET));
                        }
                        System.out.printf("server list:%s\n", serverList);
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        countDownLatch.await();
    }
    /**
     * 监听指定znode下的子znode变化,如果发生变化,就打印出来
     * @param groupName
     * @throws InterruptedException
     * @throws KeeperException
     */
    public void listen(String groupName) throws InterruptedException, KeeperException {
        int retry = 0;
        while (true) {
            try {
                Stat stat = zookeeper.exists("/" + groupName, null);
                if (null == stat) {
                    zookeeper.create("/" + groupName, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                List<String> children = zookeeper.getChildren("/" + groupName, this);
                /*获取发生改变后的服务器列表*/
                Set<String> currentServerList = new HashSet<>();
                Set<String> result = new HashSet<>();
                for (String child : children) {
                    byte[] data = zookeeper.getData("/" + groupName + "/" + child, true, null);
                    currentServerList.add(new String(data, CHARSET));
                }
                /*求出下线的服务器列表*/
                result.addAll(serverList);
                result.removeAll(currentServerList);
                if (result.size() != 0)
                    System.out.printf("server %s 下线了\n", result);
                /*求出上线的服务器列表*/
                result.clear();
                result.addAll(currentServerList);
                result.removeAll(serverList);
                if (result.size() != 0)
                    System.out.printf("server %s 上线了\n", result);
                /*更新当前服务器列表*/
                serverList = currentServerList;
            } catch (KeeperException.SessionExpiredException e) {
                e.printStackTrace();
                throw e;
            } catch (KeeperException e) {
                /*重试次数*/
                if (retry++ == MAX_RETRIES)
                    throw e;
                /*重试等待时间*/
                Thread.sleep(RETRY_TIME_OUT);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    /**
     * 指定事件类型为子节点更新
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeChildrenChanged) {
            try {
                /*发生子节点更新时*/
                listen("services");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    }
}

  1. 运行,下面图片均为ZKClient控制台打印内容。
    启动ZKClient:
    在这里插入图片描述

再启动ZKServer再停止:
在这里插入图片描述

修改ZKServer的memberName为service2,启动:
在这里插入图片描述
再停止:
在这里插入图片描述

至此,服务器动态上下线感知完成!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值