前言
配置一詞,想必大家都不陌生。畢竟程序員總是收到這樣的叮囑:不要寫死。也就是要把一些不經常變化的,可以統一維護的信息放在配置文件中,這樣當配置發生變化時就不用重新編譯代碼。
在單機系統中,往往幾個配置文件足矣。自然就是放在固定的目錄下,程序啓動時加載即可。即使修改配置,也不需要修改很多地方。但是,分佈式系統是大勢所趨,未來會作爲程序員的基本技能。如果原先的系統被拆分成了很多個節點來部署,每次配置文件發生了變化,就需要把每個節點上的配置文件都修改一次。作爲一個有追求的程序員,我們肯定不能幹這事。
於是,我們把配置文件統一放在一個地方,配置文件修改了之後,能夠及時通知所有節點重新加載。這樣即使再多的節點,也只需要修改一次配置文件。這個管理統一配置的系統就是統一配置中心。
本文主要用zookeeper的watch
機制結合響應式API,實現簡易的統一配置中心。
實現
可選的統一配置中心也不少,這裏列出幾個主要的,有興趣的同學自己研究下
統一配置中心的核心功能之一就是配置變化後,及時通知所有的節點。zookeeper的watch
機制正好可以滿足這樣的需求,所以本文利用zookeeper的watch
機制來實現。不熟悉zookeeper異步非阻塞API的朋友可以先看這篇練練手:Java操作Zookeeper
整個實現過程基於響應式編程模型,所以一步步講解不是很方便。有興趣的同學可以把源碼拷下來多運行幾次,自然會慢慢弄懂。
獲取對象
首先肯定要獲取zookeeper對象
package com.sicimike.zk;
import com.google.common.base.Joiner;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* @author sicimike
* @create 2020-05-03 21:15
*/
public class ZookeeperUtil {
private static final String[] ZK_SERVER = {
"192.168.1.101:2181", "192.168.1.102:2181",
"192.168.1.103:2181", "192.168.1.104:2181",
};
private static final int SESSION_TIMEOUT = 3000;
private static final CountDownLatch latch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
public static ZooKeeper newInstance() throws IOException, InterruptedException {
zooKeeper = new ZooKeeper(Joiner.on(",").join(ZK_SERVER), SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
switch (event.getState()) {
case SyncConnected:
latch.countDown();
break;
case Expired:
break;
}
}
});
latch.await();
return zooKeeper;
}
}
實現監聽器
本文是利用zookeeper的watch
機制來實現配置的更新通知,所以需要實現監聽器來註冊事件。因爲這個監聽器需要同時監聽Znode創建、刪除、以及Znode數據被修改等事件,所以需要實現三個接口。
package com.sicimike.zk;
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;
/**
* @author sicimike
* @create 2020-05-03 21:18
*/
public class CommonWatcher implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
private CountDownLatch latch = new CountDownLatch(1);
private DF17Config config;
private ZooKeeper zooKeeper;
public void await() throws InterruptedException {
zooKeeper.exists(config.getPath(), this, this, "");
latch.await();
}
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
System.out.println("None even");
break;
case NodeCreated:
System.out.println("NodeCreated even");
zooKeeper.getData(config.getPath(), this, this, "");
break;
case NodeDeleted:
System.out.println("NodeDeleted even");
config.setValue("");
latch = new CountDownLatch(1);
break;
case NodeDataChanged:
System.out.println("NodeDataChanged even");
zooKeeper.getData(config.getPath(), this, this, "");
break;
case NodeChildrenChanged:
System.out.println("NodeChildrenChanged even");
break;
}
}
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
if (stat != null) {
zooKeeper.getData(path, this, this, "");
}
}
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
if (data != null) {
config.setValue(new String(data));
latch.countDown();
}
}
public void setZooKeeper(ZooKeeper zooKeeper) {
this.zooKeeper = zooKeeper;
}
public void setConfig(DF17Config config) {
this.config = config;
}
}
核心配置
核心配置類也就是業務真正需要的類,包含了業務需要的所有配置,此處的配置內容抽象成一個String
。
package com.sicimike.zk;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.Objects;
/**
* 配置文件
*
* @author sicimike
* @create 2020-05-03 21:21
*/
public class DF17Config {
private static final String CONFIG_PATH = "/DF-config";
private String path;
private String value;
public static void getConfig() throws InterruptedException, IOException {
ZooKeeper zooKeeper = ZookeeperUtil.newInstance();
DF17Config config = new DF17Config();
config.setPath(CONFIG_PATH);
CommonWatcher commonWatcher = new CommonWatcher();
commonWatcher.setZooKeeper(zooKeeper);
commonWatcher.setConfig(config);
commonWatcher.await();
while (true) {
if (Objects.equals(config.getValue(), "")) {
commonWatcher.await();
}
System.out.println("config: " + config.getValue());
Thread.sleep(3000);
}
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return path;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
測試類
package com.sicimike.zk;
import java.io.IOException;
/**
* @author sicimike
* @create 2020-05-03 12:05
*/
public class ZookeeperDemo {
public static void main(String[] args) throws IOException, InterruptedException {
DF17Config.getConfig();
}
}
執行
執行ZookeeperDemo
類,等待程序連上zookeeper之後,再利用zkCli
客戶端操作產生事件,查看程序的輸出。操作的目錄爲/DF-config
。
NodeCreated even
config: hello DF-17
config: hello DF-17
config: hello DF-17
NodeDataChanged even
config: hello DF-18
config: hello DF-18
config: hello DF-18
NodeDeleted even
NodeCreated even
config: hello DF-17
config: hello DF-17
config: hello DF-17
此處博主先執行
create /DF-config "hello DF-17"
可以看到程序檢測到了NodeCreated even
事件,並且輸出了最新的配置。然後執行
set /DF-config "hello DF-18"
程序檢測到了NodeDataChanged even
事件,並輸出了最新的配置。接着執行
delete /DF-config
程序檢測到了NodeDeleted even
事件,並被阻塞,停止了輸出,因爲刪除節點,相當於配置也丟失了。
最後再次創建這個Znode,程序依然可以繼續執行。
總結
本文利用zookeeper簡單實現了配置中心的核心功能,主要是爲了熟悉zookeeper的核心API以及初步嘗試響應式編程。