上一篇(基于zookeeper实现分布式配置中心(一))讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性,简单实现一个分布式配置中心。
配置中心的优势
1、各环境配置集中管理。
2、配置更改,实时推送,jvm环境变量及时生效。
3、依靠配置变更,动态扩展功能,减少二次上线带来的成本。
4、减少开发人员、运维人员修改配置带来的额外开销。
配置中心架构图
配置中心功能
1、配置管理平台中,操作人员可以创建项目所属系统、应用名称、实例名称、配置分组等信息。
2、配置管理平台中,操作人员可以上传配置文件,对属性有增、删、改、查的操作。
3、配置内容通过配置管理平台后台服务进行持久化(保存到数据库中)。
4、操作人员通过配置平台进行推送操作,将配置推送到zk集群相应结点(/cfgcenter/系统名称/应用名称/实例名称/分组名称)。
5、配置中心客户端监听zk集群中对应结点数据发生变化,读取变更后的内容,解析内容,刷新本地备份(分布式容灾)和Spring环境变量。
6、配置中心客户端如果和zk集群丢失连接,将加载本地本分配置到Spring环境变量。
7、配置中心客户端重新和zk集群建立连接,从zk集群中拉取最新配置内容,解析配置,刷新本地备份(分布式容灾)和Spring环境变量。
8、配置中心客户端将Spring环境变量刷新之后,动态刷新依赖配置中心配置的bean。
配置中心代码视图
配置中心客户端设计解析
配置中心客户端初始化
@Component public class CfgcenterInit implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, ApplicationListener<ApplicationEvent> { private static Logger LOGGER = LoggerFactory.getLogger(CfgcenterInit.class); @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { LOGGER.info("初始化配置中心客户端监听器..."); ZKClient.getInstance() .init(); } else if (event instanceof RefreshEvent) { ZKClient.getInstance() .getAeb() .post(event); } else if (event instanceof ContextClosedEvent) { if (null != ZKClient.getInstance().getCw()) { ZKClient.getInstance() .getCw() .close(); } } } @Override public void initialize(ConfigurableWebApplicationContext cac) { try { ZookeeperProperties zookeeperProperties = ConfigurationBinder .withPropertySources(cac.getEnvironment()) .bind(ZookeeperProperties.class); if (!zookeeperProperties.isEnabled()) { LOGGER.info("未开启配置中心客戶端..."); return; } ZKClient.getInstance() .binding( zookeeperProperties , new ZookeeperConfigProperties() , cac ); } catch (Exception e) { LOGGER.error("配置中心客户端初始化异常...", e); } } }
1、ApplicationContextInitializer#initialize方法中,获取zk连接信息配置,如果开启配置中心客户端,将ZookeeperProperties(zk集群连接信息)、ZookeeperConfigProperties(客户端监听zk集群结点信息)、ConfigurableWebApplicationContext (应用上下文)绑定到ZKClient实例中去。
2、ApplicationListener#onApplicationEvent方法中监听ContextRefreshedEvent(初始化配置中心客户端监听器)、RefreshEvent(配置刷新事件,通过guava的事件总线进行推送)、ContextClosedEvent(关闭配置中心客户端资源)。
配置中心客户端监听器
public class ConfigWatcher implements Closeable, TreeCacheListener { private static Logger LOGGER = LoggerFactory.getLogger(ConfigWatcher.class); private AtomicBoolean running = new AtomicBoolean(false); private String context; private CuratorFramework source; private HashMap<String, TreeCache> caches; public ConfigWatcher(String context, CuratorFramework source) { this.context = context; this.source = source; } public void start() { if (this.running.compareAndSet(false, true)) { this.caches = new HashMap<>(); if (!context.startsWith("/")) { context = "/" + context; } try { TreeCache cache = TreeCache.newBuilder(this.source, context).build(); cache.getListenable().addListener(this); cache.start(); this.caches.put(context, cache); // no race condition since ZookeeperAutoConfiguration.curatorFramework // calls curator.blockUntilConnected } catch (KeeperException.NoNodeException e) { // no node, ignore } catch (Exception e) { LOGGER.error("Error initializing listener for context " + context, e); } } } @Override public void close() { if (this.running.compareAndSet(true, false)) { for (TreeCache cache : this.caches.values()) { cache.close(); } this.caches = null; } } @Override public void childEvent(CuratorFramework client, TreeCacheEvent event) { TreeCacheEvent.Type eventType = event.getType(); switch (eventType) { case INITIALIZED: LOGGER.info("配置中心客户端同步服务端状态完成..."); refreshEnvAndBeans(event); break; case NODE_REMOVED: case NODE_UPDATED: refreshEnvAndBeans(event); break; case CONNECTION_SUSPENDED: case CONNECTION_LOST: LOGGER.info("配置中心客户端与服务端连接异常..."); break; case CONNECTION_RECONNECTED: LOGGER.in