在 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下的子節點,因爲我使用的是臨時有序節點,服務停止時改節點會自動刪除(根據應用場景來決定使用持久節點還是臨時節點)
以上爲個人學習總結,如有錯誤還請大家提出來共同探討,希望大家多多留言評論