分布式配置是一种将配置信息分散存储在不同的节点或服务器上,以实现系统配置的统一管理和灵活调整的技术。在分布式系统中,配置信息可能包括系统参数、数据库连接信息、服务地址等,通过分布式配置可以实现这些信息的集中管理和动态更新。
分布式配置可以提高系统的可扩展性和可靠性,同时也能够简化系统的部署和维护工作。常见的分布式配置方案包括使用配置中心、分布式数据库或者分布式文件系统等。
上图中,配置中心用来几种管理配置信息,当配置信息被需改时,会主动通知各个服务,服务收到通知后从配置中心主动拉取配置信息。
公司需要开发一款产品,刚开始的时候,由于用户量、业务较少,仅需要一台服务器就可完成服务部署,服务部署、配置管理也非常容易。然而,随着业务的发展,单一节点的服务无法满足业务的飞速发展,后面就出现了分布式、集群的概念,到了现在就形成了微服务,这就导致服务部署、配置管理非常麻烦。
假设我们线上有很多个微服务分布在不同的服务器上,其中一个微服务,我们就叫它 “服务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 事件 重新拉取配置... 配置信息:{}
最后,我们看看下面动态图: