利用zookeeper手動實現配置中心

前言

配置一詞,想必大家都不陌生。畢竟程序員總是收到這樣的叮囑:不要寫死。也就是要把一些不經常變化的,可以統一維護的信息放在配置文件中,這樣當配置發生變化時就不用重新編譯代碼。

在單機系統中,往往幾個配置文件足矣。自然就是放在固定的目錄下,程序啓動時加載即可。即使修改配置,也不需要修改很多地方。但是,分佈式系統是大勢所趨,未來會作爲程序員的基本技能。如果原先的系統被拆分成了很多個節點來部署,每次配置文件發生了變化,就需要把每個節點上的配置文件都修改一次。作爲一個有追求的程序員,我們肯定不能幹這事。

於是,我們把配置文件統一放在一個地方,配置文件修改了之後,能夠及時通知所有節點重新加載。這樣即使再多的節點,也只需要修改一次配置文件。這個管理統一配置的系統就是統一配置中心

本文主要用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以及初步嘗試響應式編程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章