ZooKeeper实践:(2)配置管理
一、 前言
配置是每个程序不可或缺的一部分,配置有多重方式:xml、ini、property、database等等,从最初的单机环境到现在的分布式环境。
1. 以文件的格式存储配置,修改任何都要改程序,重新发布,重新部署,经常出现数据不一致的问题,配置错误,会造成更大的问题。
2. 以数据库+缓存配置,解决了动态更新配置的方式,但是存在数据库的单点问题,一单数据库出现问题,更新的操作都会失败,造成配置不能持久化。
随着机器数的增多,逐个修改配置是一件不合理的做法,使用Zookeeper可以实现数据发布与订阅,顾名思义就是把数据存储在Zookeeper的节点上,供订阅方进行获取,实现配置信息的集中式管理和动态更新。解决以上问题
二、需求
1. 实现集中式配置管理
2. 实现灰度发布(先发布一台机器)
三、技术原理
1. 配置信息存储在Zookeeper的某个目录下中,目录的命名按照一定的规则(例如:应用ID),一组相同功能的应用集群监控一个节点。
2. 配置发生变化,应用服务器会监听到事件,然后从Zookeeper获取新的配置信息到更新应用服务器的本地缓存中。
3. 做一个简单的界面展示集群的所有机器,可以拉出来一台灰度发布。
4. 应用服务器在接收到新的配置信息时判断是否是灰度发布的,如果是判断机器的IP,如果IP相同就更新本地缓存,否则不更新。
四:架构体系
五:流程图
六:相关代码
client模拟:
package com.zk.config.manager; import java.io.IOException; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; public class App { private CountDownLatch connectedSemaphore = new CountDownLatch(1); private ZooKeeper zooKeeper; private Object lock = new Object(); private String ip; private String rootConfig; public App(String ip, String root) { this.ip = ip; this.rootConfig = root; this.zooKeeper = connectZookeeper(); } /** * 更新缓存 * @param path * @param value */ private void updateCache(String path, String value) { //System.out.println(CacheManager.get("ips")); if (CacheManager.get("ips").contains(this.ip)) { CacheManager.add(this.ip + path, value); System.out.println(ip+"被更新了"); } } public ZooKeeper connectZookeeper() { synchronized (lock) { if (zooKeeper == null) { try { zooKeeper = new ZooKeeper("192.168.1.222:2181", 5000, new Watcher() { public void process(WatchedEvent event) { if (KeeperState.SyncConnected == event.getState()) { if (EventType.None == event.getType() && null == event.getPath()) { try { connectedSemaphore.countDown(); zooKeeper.getChildren(rootConfig, true); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } else if (EventType.NodeChildrenChanged == event.getType()) { try { System.out.println(ip); String path = event.getPath(); String value =new String(zooKeeper.getData(path, false, null)); zooKeeper.getChildren(rootConfig, true); updateCache(path,value);//模拟更新缓存 } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } ); connectedSemaphore.await(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } return zooKeeper; } }
本地缓存模拟:
package com.zk.config.manager; import java.util.HashMap; import java.util.Map; //模拟本机缓存 public class CacheManager { private static Map<String, String> map = new HashMap<String, String>(); public static void add(String key,String value) { map.put(key, value); } public static String get(String key) { return map.get(key); } }
配置管理模拟:
package com.zk.config.manager; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; public class ConfigManager { private static String rootConfigName = "/testApp"; public static void main(String[] args) throws KeeperException, InterruptedException { String ips = args[0]; // 要发布的机器IP CacheManager.add("ips", ips); App app1 = new App("192.168.1.1", rootConfigName); App app2 = new App("192.168.1.2", rootConfigName); App app3 = new App("192.168.1.3", rootConfigName); ZooKeeper zooKeeper = app1.connectZookeeper(); zooKeeper.create(rootConfigName + "/gggggg", "sds".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.delete(rootConfigName + "/gggggg", -1); Thread.sleep(60000); } }
运行结果:192.168.1.3192.168.1.1192.168.1.2192.168.1.1被更新了