一、場景 & 需求
集群上有很多個節點運行同一個任務,這個任務會有一些可能經常改變的配置參數,要求是當配置參數改變之后能夠很快地同步到每個節點上,如果將這些配置參數放在本地文件中則每次都要修改本地文件費時費力還可能會有遺漏,所以這個時候一個比較自然的想法就是將配置單獨提取出來作為一個服務,比如自己開發一個http服務器提供一個接口來獲取服務,這有兩個問題,其一是配置下發這其實是一個推模型,當配置發生改變時需要服務器去主動推給客戶端而不是客戶端不斷地去輪詢,其二是配置中心不能是單點故障,對配置中心的可用性有一定要求,這時候如果有zookeeper集群的話直接拿來作為配置中心使用不失為一種簡單的方案。
二、分析
一個配置中心的核心是什么:
1. 低延遲:配置改變后能夠盡快的將最新配置同步給每一個節點。
2. 高可用:配置中心需要能夠穩定不間斷的提供服務。
第一點可以通過zookeeper的watcher機制實現,約定一個節點用來存放配置信息,每個客戶端都監聽這個節點的NodeDataChanged事件,當配置發生改變時將最新的配置更新到這個節點上(誰更新無所謂,任意一個節點都可以更新,或者做一個另外的配置管理后台用來更新都沒有問題),這個節點觸發NodeDataChanged事件,通知所有監聽此節點NodeDataChanged事件的客戶端獲取此節點的最新值,因為watcher是一次性的,所以在獲取最新值的時候需要重新設置監聽事件,因為getData是原子性操作,所以能夠保證獲取到的一定是最新的值。這里需要注意的是存放在節點上的配置文件不宜過大,如果配置文件部分很大而每次變更的只是一部分的話或許可以考慮對其進行拆分,存放在多個節點上。
第二點的高可用性就是交由zookeeper集群來保證,在應用層面不需要做額外的工作。
下面是分布式配置管理中心簡單的示意圖:
程序運行過程中不斷的重復123步驟。
三、代碼實現
ConfigManager.java:
package cc11001100.zookeeper.configManager; import cc11001100.zookeeper.utils.ZooKeeperUtil; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; /** * 使用zookeeper做分布式配置管理的例子 * * @author CC11001100 */ public class ConfigManager { private String configNode; private ZooKeeper zooKeeper; public ConfigManager(String configNode) throws IOException { this.configNode = configNode; zooKeeper = ZooKeeperUtil.getZooKeeper(); registerConfigChangeListener(); } private void registerConfigChangeListener() { try { byte[] newConfig = zooKeeper.getData(configNode, event -> registerConfigChangeListener(), null); if (newConfig != null) { handleNewConfig(new String(newConfig, "UTF-8")); } } catch (IOException | InterruptedException | KeeperException e) { e.printStackTrace(); } } protected void handleNewConfig(String newConfig) { System.out.println(Thread.currentThread().getName() + " config changed: " + newConfig); } public void updateConfig(String newConfig) { try { zooKeeper.setData(configNode, newConfig.getBytes(), -1); } catch (InterruptedException | KeeperException e) { e.printStackTrace(); } } }
ConfigManagerTest.java:
package cc11001100.zookeeper.configManager; import cc11001100.zookeeper.utils.ZooKeeperUtil; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 測試更新 * * @author CC11001100 */ public class ConfigManagerTest { private static void sleep(int mils) { try { TimeUnit.MILLISECONDS.sleep(mils); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, KeeperException, InterruptedException { final String CONFIG_NODE = "/config-node"; ZooKeeper zooKeeper = ZooKeeperUtil.getZooKeeper(); if (zooKeeper.exists(CONFIG_NODE, false) == null) { zooKeeper.create(CONFIG_NODE, "default-config".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } // 模擬有10個客戶端,每個都有一定幾率更新配置 for (int i = 0; i < 10; i++) { new Thread(() -> { try { ConfigManager configManager = new ConfigManager(CONFIG_NODE); Random random = new Random(); sleep(random.nextInt(3000)); while (true) { if (Math.random() < 0.2) { // 節點可以自己更新配置 String newConfig = Thread.currentThread().getName() + " update, new config " + random.nextLong(); configManager.updateConfig(newConfig); } sleep(3000); } } catch (IOException e) { e.printStackTrace(); } }, "client-" + i).start(); } // 也可以有專門的配置中心來管理更新配置 Random random = new Random(); while (true) { if (Math.random() < 0.2) { String newConfig = "config master update, new config " + random.nextLong(); zooKeeper.setData(CONFIG_NODE, newConfig.getBytes(), -1); } sleep(1000); } } }
控制台輸出:
... client-4 config changed: client-5 update, new config -845834592719987179 client-5 config changed: client-5 update, new config -845834592719987179 client-3 config changed: client-5 update, new config -845834592719987179 client-6 config changed: client-5 update, new config -845834592719987179 client-1 config changed: client-5 update, new config -845834592719987179 client-7 config changed: client-5 update, new config -845834592719987179 client-2 config changed: client-5 update, new config -845834592719987179 client-8 config changed: client-5 update, new config -845834592719987179 client-9 config changed: client-5 update, new config -845834592719987179 client-0 config changed: client-5 update, new config -845834592719987179 client-1-EventThread config changed: config master update, new config -1193927892495196391 client-2-EventThread config changed: config master update, new config -1193927892495196391 client-6-EventThread config changed: config master update, new config -1193927892495196391 client-9-EventThread config changed: config master update, new config -1193927892495196391 client-8-EventThread config changed: config master update, new config -1193927892495196391 client-3-EventThread config changed: config master update, new config -1193927892495196391 client-4-EventThread config changed: config master update, new config -1193927892495196391 client-7-EventThread config changed: config master update, new config -1193927892495196391 client-5-EventThread config changed: config master update, new config -1193927892495196391 client-0-EventThread config changed: config master update, new config -1193927892495196391 client-1-EventThread config changed: client-9 update, new config -3172102246096982581 client-6-EventThread config changed: client-9 update, new config -3172102246096982581 client-0-EventThread config changed: client-9 update, new config -3172102246096982581 client-2-EventThread config changed: client-9 update, new config -3172102246096982581 client-4-EventThread config changed: client-9 update, new config -3172102246096982581 client-7-EventThread config changed: client-9 update, new config -3172102246096982581 client-8-EventThread config changed: client-9 update, new config -3172102246096982581 client-9-EventThread config changed: client-9 update, new config -3172102246096982581 client-5-EventThread config changed: client-9 update, new config -3172102246096982581 client-3-EventThread config changed: client-9 update, new config -3172102246096982581 client-8-EventThread config changed: config master update, new config -8802002496608532059 client-2-EventThread config changed: config master update, new config -8802002496608532059 client-4-EventThread config changed: config master update, new config -8802002496608532059 client-1-EventThread config changed: config master update, new config -8802002496608532059 client-6-EventThread config changed: config master update, new config -8802002496608532059 client-7-EventThread config changed: config master update, new config -8802002496608532059 client-0-EventThread config changed: config master update, new config -8802002496608532059 client-5-EventThread config changed: config master update, new config -8802002496608532059 client-9-EventThread config changed: config master update, new config -8802002496608532059 client-3-EventThread config changed: config master update, new config -8802002496608532059 client-4-EventThread config changed: client-2 update, new config -4848584377488801943 client-1-EventThread config changed: client-2 update, new config -4848584377488801943 client-6-EventThread config changed: client-2 update, new config -4848584377488801943 client-2-EventThread config changed: client-2 update, new config -4848584377488801943 client-8-EventThread config changed: client-2 update, new config -4848584377488801943 client-7-EventThread config changed: client-2 update, new config -4848584377488801943 client-5-EventThread config changed: client-2 update, new config -4848584377488801943 client-9-EventThread config changed: client-2 update, new config -4848584377488801943 client-3-EventThread config changed: client-2 update, new config -4848584377488801943 client-0-EventThread config changed: client-2 update, new config -4848584377488801943 client-6-EventThread config changed: client-9 update, new config 6591542797374556406 client-4-EventThread config changed: client-9 update, new config 6591542797374556406 client-3-EventThread config changed: client-9 update, new config 6591542797374556406 client-9-EventThread config changed: client-9 update, new config 6591542797374556406 client-1-EventThread config changed: client-9 update, new config 6591542797374556406 client-2-EventThread config changed: client-9 update, new config 6591542797374556406 client-8-EventThread config changed: client-9 update, new config 6591542797374556406 client-7-EventThread config changed: client-9 update, new config 6591542797374556406 client-0-EventThread config changed: client-9 update, new config 6591542797374556406 client-5-EventThread config changed: client-9 update, new config 6591542797374556406 client-6-EventThread config changed: client-8 update, new config -6225415529835558983 client-4-EventThread config changed: client-8 update, new config -6225415529835558983 client-8-EventThread config changed: client-8 update, new config -6225415529835558983 client-2-EventThread config changed: client-8 update, new config -6225415529835558983 client-1-EventThread config changed: client-8 update, new config -6225415529835558983 client-5-EventThread config changed: client-8 update, new config -6225415529835558983 client-0-EventThread config changed: client-8 update, new config -6225415529835558983 client-7-EventThread config changed: client-8 update, new config -6225415529835558983 client-9-EventThread config changed: client-8 update, new config -6225415529835558983 client-3-EventThread config changed: client-8 update, new config -6225415529835558983 client-1-EventThread config changed: client-4 update, new config -703954571020619435 client-4-EventThread config changed: client-4 update, new config -703954571020619435 client-8-EventThread config changed: client-4 update, new config -703954571020619435 client-6-EventThread config changed: client-4 update, new config -703954571020619435 client-2-EventThread config changed: client-4 update, new config -703954571020619435 client-7-EventThread config changed: client-4 update, new config -703954571020619435 client-0-EventThread config changed: client-4 update, new config -703954571020619435 client-5-EventThread config changed: client-4 update, new config -703954571020619435 client-3-EventThread config changed: client-4 update, new config -703954571020619435 client-9-EventThread config changed: client-4 update, new config -703954571020619435 ...
四、總結
使用zk作為配置分發的優點是低延遲、高可靠性,當然也有缺點,因為watcher是跟會話綁定的,而要維護每個會話需要一個tcp一直連接到服務器,這對集群來說也是一種負載,不過考慮到2c4g單台機器支持幾百連接並發很輕松,再加上整個zookeeper集群中會有多台機器平均一下,這點負載基本忽略了。
相關資料:
1. 一篇好TM長的關於配置中心的文章 - 阿里中間件團隊博客(廢話有點多,但很值得一看)
.