前面介紹了zk的一些基礎知識,這篇文章主要介紹下如何在java環境下獲取zk的配置信息;主要基於zk的監聽器以及回調函數通過響應式編程的思想將核心代碼糅合成一個工具類,幾乎做到了拿來即用;
在分佈式集羣中,配置信息一般都會拎出來單獨配置,這樣避免了修改配置信息時候的複雜度,我們期望當有配置變更時集羣中的節點能及時得到通知並作出響應;我們可以在每個節點中開啓任務定時來zk獲取信息,然後根據獲取信息的前後是否有差異來判斷信息是否變更,但是這種方法明顯不及時,且每次輪詢zk都會增加不必要的網絡開銷,特別是節點衆多時給ZK增加了不必要的壓力;所以我們更傾向於利用ZK的監聽和回調機制來實現類似的統一消息配置,客戶端只要註冊一個監聽器當節點信息發生變更時zk會主動觸發對應事件,客戶端監聽對應事件作出響應即可;下面先貼出代碼流程圖,然後再貼出關鍵代碼
代碼執行流程:
關鍵代碼如下:
1、測試類
package com.darling.service.zookeeper;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @description: 使用響應式編程思想實現基於ZK的分佈式統一配置功能
* @author: dll
* @date: Created in 2022/11/1 12:21
* @version:
* @modified By:
*/
@Slf4j
public class ZkConfigTest {
ZooKeeper zkClient;
@Before
public void conn (){
zkClient = ZkUtil.getZkClient();
}
@After
public void close (){
try {
zkClient.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void test() throws InterruptedException {
ConfigData configData = new ConfigData();
ZkConfigUtil watchAndCallBack = new ZkConfigUtil();
watchAndCallBack.setZkClient(zkClient);
watchAndCallBack.setConfigData(configData);
watchAndCallBack.await();
String config = configData.getConfig();
while(true){
if(configData.getConfig().equals("")){
System.out.println("conf diu le ......");
watchAndCallBack.await();
}else{
System.out.println(configData.getConfig());
}
// 此處睡眠的原因是爲了便於日誌打印
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、關鍵工具類
package com.darling.service.zookeeper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
/**
* @description:
* @author: dll
* @date: Created in 2022/11/1 12:40
* @version:
* @modified By:
*/
@Data
@Slf4j
public class ZkConfigUtil implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
ZooKeeper zkClient;
ConfigData configData;
CountDownLatch cc = new CountDownLatch(1);
private final String nodePath = "/config";
/**
* 異步獲取數據getData方法的回調
* @param rc 狀態碼
* @param path 路徑
* @param ctx 上線文
* @param data 節點的數據
* @param stat 元數據信息
*/
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
if (data != null ){
configData.setConfig(new String(data));
cc.countDown();
}
}
/**
* exists方法的回調,當數據存在時會調回調函數
* @param rc 狀態碼
* @param path 路徑
* @param ctx 上下文
* @param stat 元數據信息,可通過其是否爲空判斷是否有數據
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
log.info("進入exists方法的回調,stat:{}");
if (stat != null) {
log.info("進入exists方法的回調,stat 不爲空");
// 表示path節點有數據了,可以通過getData獲取
zkClient.getData(nodePath, this, this,"123");
}
}
/**
* 節點變更的監聽器
* @param event
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
log.info("節點被創建,path:{}",event.getPath());
// 節點創建後獲取一遍數據還有個額外的不可忽略的作用:即重新註冊了watch,否則根據zk的watch只生效一次的規則,不會再次出發watch
zkClient.getData(nodePath, this, this,"123");
break;
case NodeDeleted:
log.info("節點被刪除,path:{}",event.getPath());
configData.setConfig("");
cc = new CountDownLatch(1);
break;
case NodeDataChanged:
log.info("節點被修改,path:{}",event.getPath());
// 節點修改獲取數據的作用同節點創建
zkClient.getData(nodePath, this, this,"123");
break;
case NodeChildrenChanged:
break;
}
}
public void await() {
/**
* 直接獲取配置前先判斷配置存不存在
* 第一個參數path:配置存放的節點路徑
* 第二個參數watch:path的監聽,當path有變動時回調通知
* 第三個參數StatCallback:如果path下有數據會回調此方法,可以通過Stat是否爲空判斷path是否有數據
*/
zkClient.exists(nodePath, this,this ,"PPP");
try {
cc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}