zookeeper

文章目录
Zookeeper简介
什么是Zookeeper?
Zookeeper的功能
Zookeeper工作机制
Zookeeper应用场景
Zookeeper安装
zookeeper分布式安装
Zookeeper命令行
Zookeeper选举机制
为什么ZK集群需要奇数台服务器?
Zookeeper服务
数据模型
操作
Zookeeper会话
状态
JAVA API
基本操作
服务器动态上下线感知
Zookeeper简介
Apache Zookeeper:把Zookeeper拆开来读,zoo-keeper,即动物园管理员。致力于开发和维护开源服务器,实现高度可靠的分布式协调。
1
什么是Zookeeper?
Zookeeper是一种集中式服务,用于配置维护,域名服务,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式。每次使用这些服务时,都需要做大量重复的操作。即使正确完成这些操作,这些服务的不同实现也会在部署应用程序时导致管理复杂性。而Zookeeper的目标就是封装好复制易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

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

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

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

Zookeeper目录树结构:

根目录状态信息:

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

服务器在启动时会在zk集群创建一个临时节点,临时节点在断开连接时会被删除。
“监听”客户端会获取指定路径下的节点信息,当节点信息发生改变时,会收到通知。
上图中是一个观察者设计模式的实现,“监听”客户端扮演具体观察者角色,而服务器扮演具体被观察者角色。
Zookeeper应用场景
集群统一配置管理
集群统一命名服务
注册中心
服务器的动态上下线感知
负载均衡
分布式锁
Zookeeper安装
下载安装包,zookeeper-3.4.10
链接: https://pan.baidu.com/s/1PuLqMdVBu6T9nqIUBcAURg 提取码: wwpr
上传安装包到linux
解压安装包
tar -zxvf zookeeper-3.4.10.tar.gz
把 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

启动zk
bin/zkServer.sh start

jsp查看启动

查看状态
bin/zkServer.sh status

其中Mode:standalone代表是单机模式
启动客户端
bin/zkCli.sh

查看根目录状态
stat /

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

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

修改配置文件
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

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

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

修改环境变更
vi /etc/profile
export ZOOKEEPER_HOME=/home/even/hd/zookeeper-3.4.10
export PATH=PATH: PATH:PATH:ZOOKEEPER_HOME/bin

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

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

查看状态
bin/zkServer.sh status

此处,我是先启动hd-even-01,然后再启动hd-even-03,最后启动hd-even-02机器。因此,hd-even-03的zookeeper是leader。

启动客户端
bin/zkCli.sh

Zookeeper命令行
启动客户端:zkCli.sh
查看帮助:help
查看当前znode所包含的内容:ls /
创建节点:create /even 数据
创建短暂znode:create -e /zoo even
创建带序号的znode:create -s /zoo1 even
创建短暂带序号的znode:create -e -s /zoo2 even
查看此节点的详细信息:ls2 /
获取节点值监听:get /even watch(可为空)
监听路径:ls / watch
修改znode数据:set /even 需要设置的数据
删除节点:delete /even
递归删除:rmr /zoo1
查看节点状态信息: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,因此,奇数台服务器的好处有两点:

2n+1和2n+2台机器的容灾能力相同。
2n+1和2n+2台机器组成的zookeeper集群,都允许最多宕机n台。因此,容灾能力一样的情况下,只需要2n+1台机器就可以了。
降低集群启动时因脑裂导致集群不可用的问题。
因网络问题,导致部分节点不可达,使得集群内部出现多个小集群,叫做脑裂。在启动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重要性质:

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

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

观察
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
exists NodeCreated NodeDeleted NodeDataChanged
getData NodeDeleted NodeDataChanged
getChildren NodeChildrenChanged NodeDeleted NodeChildrenChanged
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权限 允许的操作
CREATE create()
READ getChildren/getData
WRITE setData
DELETE delete()
ADMIN setACL
其中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
2
3
4
5
6
基本操作
新建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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
创建节点:
/
*
* 创建组
*
* @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
2
3
4
5
6
7
8
9
10
11
12
创建临时子节点:
/
*
* 组成员注册
*
* @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
2
3
4
5
6
7
8
9
10
11
12
13
14
显示子节点:
/
*
* 显示组成员
*
* @param groupName 组名
* @throws KeeperException
* @throws InterruptedException
/
public void list(String groupName) throws KeeperException, InterruptedException {
String path = ZOO_ROOT_PATH + groupName;
List children = zooKeeper.getChildren(path, null);
if (children.isEmpty())
System.out.println(“此分组没有成员!!”);
for (String child : children) {
System.out.println(child);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
删除节点:
/
*
* 删除组,delete方法不能递归删除,需要先把组内成员删除
*
* @param groupName
* @throws KeeperException
* @throws InterruptedException
/
public void deleteGroup(String groupName) throws KeeperException, InterruptedException {
String path = ZOO_ROOT_PATH + groupName;
List children = zooKeeper.getChildren(path, null);
for (String child : children) {
zooKeeper.delete(child, -1);
}
zooKeeper.delete(path, -1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
关闭连接:
/
*
* 关闭连接
*
* @throws InterruptedException
*/
public void close() throws InterruptedException {
zooKeeper.close();
}
1
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
10
11
12
13
14
服务器动态上下线感知
模拟服务器注册信息,即创建临时子节点,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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    监听服务器列表变化,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 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 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 children = zookeeper.getChildren("/" + groupName, this);
      /获取发生改变后的服务器列表/
      Set currentServerList = new HashSet<>();
      Set 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
运行,下面图片均为ZKClient控制台打印内容。
启动ZKClient:

再启动ZKServer再停止:

修改ZKServer的memberName为service2,启动:

再停止:

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值