在 ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。任何节点的变化(包含节点自身的增加,删除,数据更新,子节点的变化),我们都可以在关心的节点上注册一个watcher,当该节点的数据发生变化,zookeeper会查看该节点被哪些客户端监听并通知客户端进行相应的更新处理操作,这属于观察者模式。
下面来谈谈我对观察者模式的理解:观察者模式必须包含两个角色,观察者与被观察者,观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。并且观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。例如交通信号灯就是被观察者,汽车司机及行人都是观察者,当交通信号灯发生变化,汽车司机及行人根据信号灯来决定应该通过还是继续等待。
java代码实战
1.创建简单的maven工程,增加zookeeper依赖,pom文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jioabao</groupId>
<artifactId>zookeeperDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>zookeeperDemo</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2.使用java代码实现znode的增删改查
package com.jioabao.myPractice;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class ZkOperationDemo {
private static Logger logger = LoggerFactory.getLogger(ZkOperationDemo.class);
public static void main(String[] aegs){
try {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, null);
String nodePath = "/zhangDaDa";
//1.查看节点是否存在
Stat stat = zk.exists(nodePath, false);
if (stat == null)
System.out.println("1.第一次查询节点信息不存在");
else
System.out.println("1.第一次查询节点信息为:"+stat.toString());
//2.如果节点不存在则创建节点
if(stat == null){
//ZooKeeper.create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
//第三个参数acl暂时写为ZooDefs.Ids.OPEN_ACL_UNSAFE,createMode为持久性节点
//zookeeper的acl(访问控制)会单独出一节讲解
//CreateMode已在https://blog.csdn.net/zh2508/article/details/85339860 中提到
zk.create(nodePath, "111".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
stat = zk.exists(nodePath, false);
if (stat == null)
System.out.println("2.创建节点后节点信息不存在");
else
System.out.println("2.创建节点后节点信息为:"+stat.toString());
}
//3.查询节点数据
byte[] b = zk.getData(nodePath, false, null);
System.out.println("3.查询到节点数据信息为"+new String(b));
//4.更新节点数据
zk.setData(nodePath, "222".getBytes(), stat.getVersion());
b = zk.getData(nodePath, false, null);
System.out.println("4.更新后查询到节点数据信息为"+new String(b));
//5.删除节点
zk.delete(nodePath, zk.exists(nodePath, false).getVersion());
stat = zk.exists(nodePath, false);
if (stat == null)
System.out.println("5.删除节点后节点信息不存在");
else
System.out.println("5.删除节点后节点信息为:"+stat.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
3.模拟应用服务端水平扩展后通知客户端变更
客户端代码:
package com.jioabao.myPractice;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
public class ZkClient{
private static Logger logger = LoggerFactory.getLogger(ZkClient.class);
public static void main(String[] args){
try {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, null);
String nodePath = "/zkTest";
if (zk.exists(nodePath, false) == null){//节点不存在先创建节点
zk.create(nodePath, "111".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
List<String> children = null;
try {
//在获取子节点信息时注册watcher
children = zk.getChildren("/zkTest", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("Event Received: "+ event.toString());
/**
* 有以下几种事件
* None
* NodeCreated 节点被创建
* NodeDeleted 节点被删除
* NodeDataChanged 节点数据更新
* NodeChildrenChanged 子节点变更
*/
if (event.getType() == Event.EventType.NodeChildrenChanged) {
try {
//获取子节点信息并再次监听
List<String> children = zk.getChildren(Constants.ROOT_NODE, this);
System.out.println("---------------子节点信息变更-----------");
System.out.println("变更后子节点信息为"+ children);
} catch (KeeperException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
} catch (KeeperException e) {
e.printStackTrace();
}
System.out.println("子节点信息为" + children);
Thread.sleep(Integer.MAX_VALUE);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
服务端代码:
package com.jioabao.myPractice;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.management.ManagementFactory;
public class ZkServer {
private static Logger logger = LoggerFactory.getLogger(ZkServer.class);
public static void main(String[] aegs){
try {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, null);
String nodePath = "/zkTest";
if (zk.exists(nodePath, false) == null){//节点不存在先创建节点
zk.create(nodePath, "111".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
String name = ManagementFactory.getRuntimeMXBean().getName();
int index = name.indexOf('@');
Long processId = Long.parseLong(name.substring(0, index));
String childNodePath = nodePath + "/" + processId;
//创建临时有序节点
zk.create(childNodePath, "127.0.0.1:8080".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("服务注册成功,其下的节点有" + zk.getChildren(nodePath, false));
Thread.sleep(Integer.MAX_VALUE);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
验证:
步骤一:启动客户端
步骤二:启动服务端
步骤三:查看客户端控制台
步骤四:再起一个服务端,客户端控制台信息如下
步骤五:关闭其中一个服务端,客户端控制台信息如下
步骤六:关闭客户端及服务端,通过dos窗口查看/zkTest下的子节点,因为我使用的是临时有序节点,服务停止时改节点会自动删除(根据应用场景来决定使用持久节点还是临时节点)
以上为个人学习总结,如有错误还请大家提出来共同探讨,希望大家多多留言评论