以前有了解过disconf,也知道它是基于zookeeper来作的,可是对于其运行原理不太了解,趁着周末,debug下源码,也算是不枉费周末大好时光哈 :) 。关于这篇文章,笔者主要是参考disconf源码和官方文档,如有不正确地方,感谢评论区指正交流~前端
disconf是一个分布式配置管理平台(Distributed Configuration Management Platform),专一于各类 分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务,是一套完整的基于zookeeper的分布式配置统一解决方案。disconf目前已经被多个公司在使用,包括百度、滴滴出行、银联、网易、拉勾网、苏宁易购、顺丰科技 等知名互联网公司。disconf源码地址 https://github.com/knightliao/disconf ,官方文档 https://disconf.readthedocs.io/zh_CN/latest/ 。java
目前disconf包含了 客户端disconf-Client和 管理端disconf-Web两个模块,均由java实现。服务依赖组件包括Nginx、Tomcat、Mysql、ZooKeeper,nginx提供反向代理(disconf-web是先后端分离的),Tomcat是后端web容器,配置存储在mysql上,基于zookeeper的wartch模型,实时推送。注意,disconf优先读取本地文件,disconf只支持应用对配置的读操做,经过在disconf-web上更新配置,而后由zookeeper通知到服务实例,最后服务实例去disconf-web端获取最新配置并更新到本地。node
disconf 功能特色:mysql
功能特色描述图nginx
disconf 架构图git
分析disconf,最好是在本地搭建一个disconf-web环境,方便调试代码,具体步骤可参考官方文档,使用disconf-client,只须要在pom引入依赖便可:github
<dependency> <groupId>com.baidu.disconf</groupId> <artifactId>disconf-client</artifactId> <version>2.6.36</version> </dependency>
对于开发人员来讲,最多接触的就是disconf-web配置和disconf-client了,disconf-web配置官方文档已经很详细了,这里就来不及解释了,抓紧上车,去分析disconf-client的实现,disconf-client最重要的内容就是disconf-client初始化流程和配置动态更新机制。disconf的功能是基于Spring的(初始化是在Spring的BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry开始的,配置动态更新也是要更新到Spring IoC中对应的bean),因此使用disconf,项目必须基于Spring。web
<!-- 使用disconf必须添加如下配置 --> <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean" destroy-method="destroy"> <property name="scanPackage" value="com.luo.demo"/> </bean> <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond" init-method="init" destroy-method="destroy"> </bean>
DisconfMgrBean#postProcessBeanDefinitionRegistry方法主要作的3件事就是扫描(firstScan)、注册DisconfAspectJ 和 bean属性注入。sql
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // scanPackList包括disconf.xml中DisconfMgrBean.scanPackage List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN); // 1. 进行扫描 DisconfMgr.getInstance().setApplicationContext(applicationContext); DisconfMgr.getInstance().firstScan(scanPackList); // 2. register java bean registerAspect(registry); }
protected synchronized void firstScan(List<String> scanPackageList) { // 导入配置 ConfigMgr.init(); // registry Registry registry = RegistryFactory.getSpringRegistry(applicationContext); // 扫描器 scanMgr = ScanFactory.getScanMgr(registry); // 第一次扫描并入库 scanMgr.firstScan(scanPackageList); // 获取数据/注入/Watch disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry); disconfCoreMgr.process(); }
进行包扫描是使用Reflections来完成的,获取路径下(好比xxx/target/classes)某个包下符合条件(好比com.luo.demo)的资源(reflections),而后从reflections获取某些符合条件的资源列表,以下:json
/** * 扫描基本信息 */ private ScanStaticModel scanBasicInfo(List<String> packNameList) { ScanStaticModel scanModel = new ScanStaticModel(); // 扫描对象 Reflections reflections = getReflection(packNameList); scanModel.setReflections(reflections); // 获取DisconfFile class Set<Class<?>> classdata = reflections.getTypesAnnotatedWith(DisconfFile.class); scanModel.setDisconfFileClassSet(classdata); // 获取DisconfFileItem method Set<Method> af1 = reflections.getMethodsAnnotatedWith(DisconfFileItem.class); scanModel.setDisconfFileItemMethodSet(af1); // 获取DisconfItem method af1 = reflections.getMethodsAnnotatedWith(DisconfItem.class); scanModel.setDisconfItemMethodSet(af1); // 获取DisconfActiveBackupService classdata = reflections.getTypesAnnotatedWith(DisconfActiveBackupService.class); scanModel.setDisconfActiveBackupServiceClassSet(classdata); // 获取DisconfUpdateService classdata = reflections.getTypesAnnotatedWith(DisconfUpdateService.class); scanModel.setDisconfUpdateService(classdata); return scanModel; }
public static DisconfCoreMgr getDisconfCoreMgr(Registry registry) throws Exception { FetcherMgr fetcherMgr = FetcherFactory.getFetcherMgr(); // 不开启disconf,则不要watch了 WatchMgr watchMgr = null; if (DisClientConfig.getInstance().ENABLE_DISCONF) { // Watch 模块 watchMgr = WatchFactory.getWatchMgr(fetcherMgr); } return new DisconfCoreMgrImpl(watchMgr, fetcherMgr, registry); } public static WatchMgr getWatchMgr(FetcherMgr fetcherMgr) throws Exception { synchronized(hostsSync) { // 从disconf-web端获取 Zoo Hosts信息,及zookeeper host和zk prefix信息(默认 /disconf) hosts = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooHostsUrl(DisClientSysConfig .getInstance() .CONF_SERVER_ZOO_ACTION)); zooPrefix = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooPrefixUrl(DisClientSysConfig .getInstance () .CONF_SERVER_ZOO_ACTION)); /** * 初始化watchMgr,这里会与zookeeper创建链接,若是/disconf节点不存在会新建 */ WatchMgr watchMgr = new WatchMgrImpl(); watchMgr.init(hosts, zooPrefix, DisClientConfig.getInstance().DEBUG); return watchMgr; } return null; }
往Spring中注册一个aspect类DisconfAspectJ,该类会对@DisconfFileItem注解修饰的方法作切面,功能就是当获取bean属性值时,若是开启了DisClientConfig.getInstance().ENABLE_DISCONF,则返回disconf仓库中对应的属性值,不然返回bean实际值。注意:目前版本的disconf在更新仓库中属性值后会将bean的属性值也一同更改,因此,目前DisconfAspectJ类做用已不大,没必要理会,关于该类的讨论可参考issue DisconfAspectJ 拦截的做用?
bean属性注入是从DisconfMgr.secondScan开始的:
protected synchronized void secondScan() { // 扫描回调函数,也就是注解@DisconfUpdateService修饰的配置更新回调类,该类需实现IDisconfUpdate if (scanMgr != null) { scanMgr.secondScan(); } // 注入数据至配置实体中 if (disconfCoreMgr != null) { disconfCoreMgr.inject2DisconfInstance(); } }
/** * 更新消息: 某个配置文件 + 回调 */ @Override public void updateOneConfAndCallback(String key) throws Exception { // 更新 配置 updateOneConf(key); // 回调 DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key); callUpdatePipeline(key); }