采用 Zookeeper 实现配置中心

784 阅读6分钟

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

我们知道 Java 应用程序经常性的会依赖于一些底层的配置信息。在我们实际业务开发中,我们一般都是把一些通用的配置信息去编写成配置文件。我们一般开发一个项目是不是有很多的配置文件,在这些配置文件里面,我们可以去配置一些比如说数据库连接信息,包括 Redis 的一些配置等等。

我们传统的这个实现方式,其实就是把配置文件以及配置信息存储到本地文件或内存中,但是我们现在是集群环境下,如果一旦项目的规模更变大,配置变更比较频繁,那么我们现在这个本地软件和内存方式的配置维护成本就比较高了,因为我们现在是集群模式下,然后如果我们的配置文件变更又比较频繁,那么我们现在每一次停机对这个配置文件内容进行修改,这样是不是维护成本是很高的呀?

Zookeeper 作为配置中心关注点

所以,我们可以使用 Zookeeper 作为分布式的配置中心来解决这些问题。那我们再去使用 Zookeeper 当作配置中心的时候,其实要关注两点:

第一点就是如何把配置信息存到 Zookeeper 中。

第二点就是当我们 Zookeeper 它的一个配置信息一旦发生改变,我们该怎么把这个改变去同步给我们当前集群中其他的 Zookeeper 服务。

Zookeeper 是如何解决这两点:

第一点,我们说它是如何存储的。其实它就是将这个配置信息存到 Zookeeper 中的一个节点中,也就是数据真正的存到的是 Zookeeper 的节点中。

第二点,我们说如何进行这个数据的同步,其实 Zookeeper 就是给这个节点去注册一个数据节点变更的 water监听,也就是对这个节点添加了一个 watch 监听,如果一旦这个节点数据发生变更,那么所有的订阅该节点的客户端都可以获取数据变更通知。

Zookeeper 作为配置中心设计思路

我们现在所举的这个案例是这样的,Java 的应用程序要去连接关系型数据库,连接关系型数据库需要 url、用户名以及密码,我们将 url、用户名以及密码,这些信息呢配置在 Zookeeper 服务内部。当这些信息一旦发生变化之后呢,我们可以通过watch 机制捕获到相应的事件读取新的配置信息。

这个案例我们的设计思路如下:

第一步:编写应用程序,连接 Zookeeper 服务器。

第二步:我们在我们所编写的工具类当中去读取 Zookeeper 服务器当中,我们提前配置在 Zookeeper 当中的相关的一系列配置信息,并把它存入本地变量,在读取信息时,一定要注册这个watcher 监听器。

第三步:当 Zookeeper 当中的配置信息一旦发生变化时,我们通过watcher 监听器捕获到这个数据发生变化了。

第四步:重新去读取配置中心当中的相关数据。

Zookeeper 作为配置中心它的设计思路是大致是这样的。接下来我们具体实现。

使用 Java 代码实现 Zookeeper 注册中心

创建一个 MyConfigCenter 类,让这个类实现 Watcher 接口,重写 process 方法,并且定义了我们要连接 Zookeeper 的服务器 ip 地址和端口号、计数器对象以及连接对象。

//  zk的连接串
String IP = "192.168.0.158:2181";
//  计数器对象
CountDownLatch countDownLatch = new CountDownLatch(1);
// 连接对象
static ZooKeeper zooKeeper;
// 重写 process 方法
public void process(WatchedEvent event) {
    try {
        // 捕获事件状态
        if (event.getType() == Event.EventType.None) {
            if (event.getState() == Event.KeeperState.SyncConnected) {
                System.out.println("连接成功");
                countDownLatch.countDown();
            } else if (event.getState() == Event.KeeperState.Disconnected) {
                System.out.println("连接断开!");
            } else if (event.getState() == Event.KeeperState.Expired) {
                System.out.println("连接超时!");
                // 超时后服务器端已经将连接释放,需要重新连接服务器端
                zooKeeper = new ZooKeeper("192.168.0.158:2181", 6000,
                        new ZKConnectionWatcher());
            } else if (event.getState() == Event.KeeperState.AuthFailed) {
                System.out.println("验证失败!");
            }
            // 当配置信息发生变化时
        } else if (event.getType() == Event.EventType.NodeDataChanged) {
            //initValue();
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

通过命令方式创建要连接的节点。

[zk: 192.168.0.158(CONNECTED) 0] create /config "config"
Created /config
[zk: 192.168.0.158(CONNECTED) 1] create /config/url "192.168.0.158:3306"
Created /config/url
[zk: 192.168.0.158(CONNECTED) 2] create /config/username "root"
Created /config/username
[zk: 192.168.0.158(CONNECTED) 3] create /config/password "root"
Created /config/password

在 MyConfigCenter 类中创建三个成员变量,并生成 get 和 set 方法。

private String url;
private String username;
private String password;


public String getUrl() {
    return url;
}


public void setUrl(String url) {
    this.url = url;
}


public String getUsername() {
    return username;
}


public void setUsername(String username) {
    this.username = username;
}


public String getPassword() {
    return password;
}


public void setPassword(String password) {
    this.password = password;
}

我们来编写一个方法,用于连接 Zookeeper 服务器,读取配置信息。初始化过程中,我们首先做的工作是打开了 Zookeeper 的链接,也就是客户端与服务端之间的链接,并且打开链接之后,去读取了 url、username 以及 password 相应的配置信息。但是别忘了,就是我们的链接在创建的过程当中,它是异步创建的。所以打开链接读取数据之前,我们还得加上计数器对象,通过计数器对象使我们的主线程休眠。

// 连接zookeeper服务器,读取配置信息
public void initValue(){
    try {
        //创建连接对象
        zooKeeper=new ZooKeeper(IP,5000,this);
        // 阻塞线程,等待连接的创建成功
        countDownLatch.await();
        //读取配置信息
        this.url=new String(zooKeeper.getData("/config/url",true,null));
        this.username=new String(zooKeeper.getData("/config/username",true,null));
        this.password=new String(zooKeeper.getData("/config/password",true,null));
    }catch (Exception e){
        e.printStackTrace();
    }
}

当连接创建成功之后,我 process 方法会被调用,这个方法调用的过程当中,我们使用计算器对象通知线程继续向下执行就可以了。

接下来呢我们为当前类去编写编写构造方法,在构造方法当中调用这个初始化数据这个方法。这样的话,我们在创建对象的过程当中,创建 MyConfigCenter 这个对象时,实际上已经初始化到一系列成员变量当中去。

//构造方法
public MyConfigCenter(){
    initValue();
}

目前这个程序有没有问题,来编写一个 main 方法。通过这个 main 方法,模拟一个客户端,通过这个客户端去创建这个读取配置信息的工具类,并且通过这个工具类去获取配置信息。

public static void main(String[] args) {
    try {
        MyConfigCenter myConfigCenter = new MyConfigCenter();
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(5000);
            System.out.println("url:"+myConfigCenter.getUrl());
            System.out.println("username:"+myConfigCenter.getUsername());
            System.out.println("password:"+myConfigCenter.getPassword());
            System.out.println("########################################");
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

最后我们来测试,启动我们的程序,看控制台的输出信息。

连接成功
url:192.168.0.158:3306
username:root
password:root

 现在我们在 Zookeeper 客户端通过命令来修改配置信息,看一下控制台输出的信息。

[zk: 192.168.0.158(CONNECTED) 15] set /config/url "192.168.0.158:3308"
连接成功
url:192.168.0.158:3308
username:root
password:root

到目前为止,我们学习完了 Zookeeper 作为配置中心的使用。