文章目录
Zookeeper简介
Apache Zookeeper:把Zookeeper拆开来读,zoo-keeper,即动物园管理员。致力于开发和维护开源服务器,实现高度可靠的分布式协调。
什么是Zookeeper?
Zookeeper是一种集中式服务,用于配置维护,域名服务,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式。每次使用这些服务时,都需要做大量重复的操作。即使正确完成这些操作,这些服务的不同实现也会在部署应用程序时导致管理复杂性。而Zookeeper的目标就是封装好复制易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
Zookeeper的功能
主要功能有两个:
1)存储数据
2)监听
存储数据,存储的是Zookeeper的节点。
监听,监听Zookeeper的数据变化。
在Zookeeper中,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据。Zookeeper使用Wathcer监听事件信息,当客户端接收到事件信息,比如连接超时、节点数据改变、子节占改变,可以调用相应的行为来处理数据。
Zookeeper目录树结构:
根目录状态信息:
Zookeeper工作机制
zookeeper是基于观察者模式设计的分布式服务管理框架。如下图所示:
- 服务器在启动时会在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:$PWDscp 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= P A T H : 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>
基本操作
- 新建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();
}
}
- 创建节点:
/**
* 创建组
*
* @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);
}
- 创建临时子节点:
/**
* 组成员注册
*
* @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);
}
- 显示子节点:
/**
* 显示组成员
*
* @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);
}
}
- 删除节点:
/**
* 删除组,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);
}
- 关闭连接:
/**
* 关闭连接
*
* @throws InterruptedException
*/
public void close() throws InterruptedException {
zooKeeper.close();
}
- 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();
}
服务器动态上下线感知
- 模拟服务器注册信息,即创建临时子节点,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);
}
}
- 监听服务器列表变化,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();
}
}
}
}
- 运行,下面图片均为ZKClient控制台打印内容。
启动ZKClient:
再启动ZKServer再停止:
修改ZKServer的memberName为service2,启动:
再停止:
至此,服务器动态上下线感知完成!!