分布式配置是一种将配置信息分散存储在不同的节点或服务器上,以实现系统配置的统一管理和灵活调整的技术。在分布式系统中,配置信息可能包括系统参数、数据库连接信息、服务地址等,通过分布式配置可以实现这些信息的集中管理和动态更新。
分布式配置可以提高系统的可扩展性和可靠性,同时也能够简化系统的部署和维护工作。常见的分布式配置方案包括使用配置中心、分布式数据库或者分布式文件系统等。

上图中,配置中心用来几种管理配置信息,当配置信息被需改时,会主动通知各个服务,服务收到通知后从配置中心主动拉取配置信息。
公司需要开发一款产品,刚开始的时候,由于用户量、业务较少,仅需要一台服务器就可完成服务部署,服务部署、配置管理也非常容易。然而,随着业务的发展,单一节点的服务无法满足业务的飞速发展,后面就出现了分布式、集群的概念,到了现在就形成了微服务,这就导致服务部署、配置管理非常麻烦。
假设我们线上有很多个微服务分布在不同的服务器上,其中一个微服务,我们就叫它 “服务A”,当 “服务A” 的 IP 地址需要变更的时候,但是 “服务A” 又对很多其他的程序提供了服务,这个时候如果没有一个配置中心,每一个使用了 “服务A” 的应用程序都要做相应的 IP 地址修改,这是一个很麻烦的事情。
为了解决这个问题,我们将通过使用 ZooKeeper 来做分布式配置,原理如下图:

上图中,有 “服务A” 和 “服务B”,如果 “服务B” 通过 set 命令修改存储在 ZooKeeper 中的配置信息,同时由于 “服务B” 通过 watch 对该配置进行了监听,因此,“服务A” 将收到配置变更的通知,收到变更通知后,立即使用 get 从 ZooKeeper 拉取最新的配置信息。
这样就实现了,当配置发生变更时及时通知其他服务的目的,也就解决了我们上面提出的问题。
下面将通过使用 zkcli 客户端工具来演示通知变更通知的效果,步骤如下:
(1)运行 zkcli 命令,打开 ZooKeeper 客户端,如下:
# 创建 /config 节点
[zk: localhost:2181(CONNECTED) 0] create /config {}
Created /config
# 使用 get 命令获取节点内容,并且使用 -w 选项监听该节点
[zk: localhost:2181(CONNECTED) 1] get -w /config
{}(2)再次运行 zkcli 命令,打开另一个 ZooKeeper 客户端,如下:
# 使用 set 命令更新配置内容
[zk: localhost:2181(CONNECTED) 0] set /config {"module":"dev"}(3)继续,观察步骤(1)创建的 ZooKeeper 客户端,如下:
[zk: localhost:2181(CONNECTED) 2] WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/config zxid: 426
通过上面输出可知,客户端收到了一个类型为 NodeDataChanged(节点数据改变)的事件。
下面我们通过 ZooKeeper 的 Java API 来模拟修改配置和监听配置的功能,完整代码如下:
package com.hxstrive.zookeeper.distributed_config;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
/**
* 使用 Zk 实现配置中心
* @author hxstrive.com
*/
public class ZkLoadConfig {
private static ZooKeeper zooKeeper;
public static void main(String[] args) throws Exception {
ZkLoadConfig app = new ZkLoadConfig();
app.init();
app.getConfig();
Scanner scanner = new Scanner(System.in);
System.out.println("按任意键退出程序...");
scanner.nextLine(); // 等待用户输入任意值
}
/**
* 初始化
* @throws Exception
*/
private void init() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
zooKeeper = new ZooKeeper("127.0.0.1:2181", 2000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println("触发了 " + watchedEvent.getType() + " 事件");
if(watchedEvent.getState() == Event.KeeperState.SyncConnected) {
countDownLatch.countDown(); // 连接成功
}
// 监听配置发生变化
if(watchedEvent.getType() == Event.EventType.NodeDataChanged) {
try {
System.out.println("重新拉取配置...");
ZkLoadConfig.this.getConfig();
} catch (Exception e){
e.printStackTrace();
}
}
}
});
System.out.println(zooKeeper);
countDownLatch.await(); // 等待连接创建成功
// 如果节点不存在,则创建节点
Stat stat = zooKeeper.exists("/config", false);
if(null == stat) {
zooKeeper.create("/config", "{}".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
/**
* 获取配置信息
* @throws Exception
*/
private void getConfig() throws Exception {
// 获取节点数据
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/config", true, stat);
System.out.println("配置信息:" + new String(data));
}
}运行示例,输出如下:
State:CONNECTING sessionid:0x0 local:null remoteserver:null lastZxid:0 xid:1 sent:0 recv:0 queuedpkts:0 pendingresp:0 queuedevents:0
触发了 None 事件
配置信息:{}
按任意键退出程序...然后,使用 zkcli 工具连接到 zookeeper,如下:
C:\Users\Administrator>zkcli Connecting to localhost:2181 ... WATCHER:: WatchedEvent state:SyncConnected type:None path:null zxid: -1 [zk: localhost:2181(CONNECTED) 0]
通过 set 命令修改 /config 节点的数据,命令如下:
[zk: localhost:2181(CONNECTED) 0] set /config "{}"此时,继续观察示例控制台,如下:
触发了 None 事件
配置信息:{}
按任意键退出程序...
触发了 NodeDataChanged 事件
重新拉取配置...
配置信息:{}最后,我们看看下面动态图:
