一、 前言 配置是每個程式不可或缺的一部分,配置有多重方式:xml、ini、property、database等等,從最初的單機環境到現在的分散式環境。 1. 以文件的格式存儲配置,修改任何都要改程式,重新發佈,重新部署,經常出現數據不一致的問題,配置錯誤,會造成更大的問題。 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.3
192.168.1.1
192.168.1.2
192.168.1.1被更新了