一、zk实现分布式配置中心代码实现:
在Zk里增加一个目录节点/username,并把配置信息存储到里面
引入zk的依赖
<!-- 引入zk依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
实现代码:
public class ZookeeperProSync implements Watcher {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private static ZooKeeper zk = null;
//用于统计ZKnode的元数据信息
private static Stat stat = new Stat();
public static void main(String[] args) {
//zookeeper配置数据存放路径,相当于在zk下新建一个存放数据节点的目录
String path = "/username";
try {
//连接zk并注册一个默认的监听器,其中192.168.199.11:2181是zk提供客户端连接的主机端口地址
//sessionTimeout的值表示客户端和服务端保持连接,心跳超时的时间。因为zk客户端和服务端是基于长连接的。所以服务器会和客户端使用
//心跳机制保持连接状态,一旦超过指定心跳时间没回复,就把客户端剔除。
zk = new ZooKeeper("192.168.199.11:2181",5000,new ZookeeperProSync() );
//等待zk连接成功的通知
connectedSemaphore.await();
//获取path目录节点的配置数据,并注册默认的监听器。 其中zk.getData相当于zk的get命令,去指定的目录下获取数据。
//而该方法的第二个参数watch=true表明当前客户端设置了一个监听器在zk的path目录下,只要path目录有变动,就立刻的
//发送一个监听事件回来通知客户端。
System.out.println(new String(zk.getData(path,true,stat)));
//模拟程序一直在运行
Thread.sleep(Integer.MAX_VALUE);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
//zk监听事件处理逻辑。
public void process(WatchedEvent watchedEvent) {
//zk成功连接的通知事件
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
//这一步的意义是当客户端和服务器连接成功,会调用process方法,而此时只是表示连接成功,即Event.EventType.None ,并没有说明任何节点发生改变。故此做这个判断,让main方法去获取连接成功后,最新的path下的数据,并且重新注册一个监听器监视自己感兴趣的目录
if(Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
connectedSemaphore.countDown();
}else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
//zk目录节点数据发生变化的通知事件
try {
System.out.println("配置已被修改,新值为:"+new String(zk.getData(watchedEvent.getPath(),true,stat)));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
启动,开始监听zk的username目录,然后在zk服务端建立username目录,并添加数据节点,然后再修改数据节点:
numChildren = 2
[zk: localhost:2181(CONNECTED) 14] create /username haizhang66666666
Created /username
[zk: localhost:2181(CONNECTED) 17] set /username haizhang777777
cZxid = 0x6
ctime = Tue Apr 28 17:38:58 CST 2020
mZxid = 0xa
mtime = Tue Apr 28 17:39:19 CST 2020
pZxid = 0x6
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 14
numChildren = 0
得到的结果:
此时当目录下的数据节点发生修改时,就可以监听到了。
对于WatchedEvent讲解
watchEvent里面有几个事件:
public static enum EventType {
None(-1), //和服务器端建立连接时的事件
NodeCreated(1), //当节点创建时的事件
NodeDeleted(2), //当节点删除时的事件
NodeDataChanged(3), //当节点下的数据状态发生修改时的事件
NodeChildrenChanged(4);//当节点下的目录结构发生变动的事件。
}
我们可以把断点定位图下:
可以发现,当成功和zk连接的时候,EventType时None类型的,并且path=null。
如果连接超时,或者心跳检测超时没回应,就会有下面的报错
表示当前客户端已经被zk剔除了。
二、注意点:
zookeeper的监听事件是一次性的
并不会包括任何数据,如果你想要获取到最新改变的数据,就得重新查询,并且重新设置监听(因为zk监听事件是一次性的)。
这里讲解下为什么zk数据发生变化时,只是把监听事件发给客户端,而不是带上最新修改数据一起发送通知给客户端。
- 因为这样操作有利于节省带宽,**最新的数据可能客户端并不在意,**所以没什么必要把数据一起通知给客户端。
- 因为zookeeper连接的节点可能会很多,zk除了要发送监听事件外,还要处理其他的客户端发送的命令,如**果发送监听事件还要带上数据,对zk的性能影响是非常大的。**最好的办法就是客户端收到监听事件,有需要再去拉取最新的数据。
zk还可以实现分布式锁等,这些实现方案无非都依赖zk的两点
文件系统+监听通知机制。