经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
disconf原理 “入坑”指南
来源:cnblogs  作者:向南l  时间:2018/10/29 10:03:57  对本文有异议

之前有了解过disconf,也知道它是基于zookeeper来做的,但是对于其运行原理不太了解,趁着周末,debug下源码,也算是不枉费周末大好时光哈 :) 。关于这篇文章,笔者主要是参考disconf源码和官方文档,若有不正确地方,感谢评论区指正交流~

disconf是一个分布式配置管理平台(Distributed Configuration Management Platform),专注于各种 分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务,是一套完整的基于zookeeper的分布式配置统一解决方案。disconf目前已经被多个公司在使用,包括百度、滴滴出行、银联、网易、拉勾网、苏宁易购、顺丰科技 等知名互联网公司。disconf源码地址 https://github.com/knightliao/disconf ,官方文档 https://disconf.readthedocs.io/zh_CN/latest/

目前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端获取最新配置并更新到本地。

 

disconf 功能特点:

  • 支持配置(配置项/配置文件)分布式管理
  • 配置发布统一化
    • 配置发布、更新统一化,同一个上线包 无须改动配置 即可在 多个环境中(RD/QA/PRODUCTION) 上线
    • 配置更新自动化:用户在平台更新配置,使用该配置的系统会自动发现该情况,并应用新配置。特殊地,如果用户为此配置定义了回调函数类,则此函数类会被自动调用
  • 上手简单,基于注解或者xml配置方式

功能特点描述图

 

disconf 架构图

 

分析disconf,最好是在本地搭建一个disconf-web环境,方便调试代码,具体步骤可参考官方文档,使用disconf-client,只需要在pom引入依赖即可:

  1. <dependency>
  2. <groupId>com.baidu.disconf</groupId>
  3. <artifactId>disconf-client</artifactId>
  4. <version>2.6.36</version>
  5. </dependency>

对于开发人员来说,最多接触的就是disconf-web配置和disconf-client了,disconf-web配置官方文档已经很详细了,这里就来不及解释了,抓紧上车,去分析disconf-client的实现,disconf-client最重要的内容就是disconf-client初始化流程配置动态更新机制。disconf的功能是基于Spring的(初始化是在Spring的BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry开始的,配置动态更新也是要更新到Spring IoC中对应的bean),所以使用disconf,项目必须基于Spring。

 

1 disconf-client 初始化流程

关于disconf-client的初始化,联想到Spring IoC流程,我们先不看代码,可以猜想一下其大致流程,disconf-client首先需要从disconf服务端获取配置,然后等到IoC流程中创建好对应的bean之后,将对应的配置值设置到bean中,这样基本上就完成了初始化流程,其实disconf的初始化实现就是这样的。
 
disconf-client的初始化开始于BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(Spring IoC初始化时,对于BeanDefinitionRegistryPostProcessor的实现类,会调用其postProcessBeanDefinitionRegistry方法),disconf的DisconfMgrBean类就是BeanDefinitionRegistryPostProcessor的实现类,DisconfMgrBean类的bean配置在哪里呢?其实就是disconf.xml中的配置,该配置是必须的,示例如下:
  1. <!-- 使用disconf必须添加以下配置 -->
  2. <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
  3. destroy-method="destroy">
  4. <property name="scanPackage" value="com.luo.demo"/>
  5. </bean>
  6. <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
  7. init-method="init" destroy-method="destroy">
  8. </bean>

 
DisconfMgrBean#postProcessBeanDefinitionRegistry方法主要做的3件事就是扫描(firstScan)、注册DisconfAspectJ 和 bean属性注入。

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  2. // scanPackList包括disconf.xml中DisconfMgrBean.scanPackage
  3. List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);
  4. // 1. 进行扫描
  5. DisconfMgr.getInstance().setApplicationContext(applicationContext);
  6. DisconfMgr.getInstance().firstScan(scanPackList);
  7. // 2. register java bean
  8. registerAspect(registry);
  9. }

 

1.1 firstScan

firstScan操作主要是加载系统配置和用户配置(disconf.properties),进行包扫描并入库,然后获取获取数据/注入/Watch。
  1. protected synchronized void firstScan(List<String> scanPackageList) {
  2. // 导入配置
  3. ConfigMgr.init();
  4. // registry
  5. Registry registry = RegistryFactory.getSpringRegistry(applicationContext);
  6. // 扫描器
  7. scanMgr = ScanFactory.getScanMgr(registry);
  8. // 第一次扫描并入库
  9. scanMgr.firstScan(scanPackageList);
  10. // 获取数据/注入/Watch
  11. disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
  12. disconfCoreMgr.process();
  13. }

进行包扫描是使用Reflections来完成的,获取路径下(比如xxx/target/classes)某个包下符合条件(比如com.luo.demo)的资源(reflections),然后从reflections获取某些符合条件的资源列表,如下:

  1. /**
  2. * 扫描基本信息
  3. */
  4. private ScanStaticModel scanBasicInfo(List<String> packNameList) {
  5. ScanStaticModel scanModel = new ScanStaticModel();
  6. // 扫描对象
  7. Reflections reflections = getReflection(packNameList);
  8. scanModel.setReflections(reflections);
  9. // 获取DisconfFile class
  10. Set<Class<?>> classdata = reflections.getTypesAnnotatedWith(DisconfFile.class);
  11. scanModel.setDisconfFileClassSet(classdata);
  12. // 获取DisconfFileItem method
  13. Set<Method> af1 = reflections.getMethodsAnnotatedWith(DisconfFileItem.class);
  14. scanModel.setDisconfFileItemMethodSet(af1);
  15. // 获取DisconfItem method
  16. af1 = reflections.getMethodsAnnotatedWith(DisconfItem.class);
  17. scanModel.setDisconfItemMethodSet(af1);
  18. // 获取DisconfActiveBackupService
  19. classdata = reflections.getTypesAnnotatedWith(DisconfActiveBackupService.class);
  20. scanModel.setDisconfActiveBackupServiceClassSet(classdata);
  21. // 获取DisconfUpdateService
  22. classdata = reflections.getTypesAnnotatedWith(DisconfUpdateService.class);
  23. scanModel.setDisconfUpdateService(classdata);
  24. return scanModel;
  25. }
View Code
获取到资源信息(比如DisconfFile 和DisconfFileItem )之后,读取DisConfFile类及其对应的DisconfFileItem信息,将它们放到disconfFileItemMap中,最后将这些信息存储到仓库DisconfCenterStore。这部分逻辑在ScanMgrImpl.firstScan方法中,整体逻辑还是比较清晰的,这里就不贴代码了。
 
扫描入库之后,就该获取数据/注入/Watch(DisconfCoreFactory.getDisconfCoreMgr()中逻辑)了。
  1. public static DisconfCoreMgr getDisconfCoreMgr(Registry registry) throws Exception {
  2. FetcherMgr fetcherMgr = FetcherFactory.getFetcherMgr();
  3. // 不开启disconf,则不要watch了
  4. WatchMgr watchMgr = null;
  5. if (DisClientConfig.getInstance().ENABLE_DISCONF) {
  6. // Watch 模块
  7. watchMgr = WatchFactory.getWatchMgr(fetcherMgr);
  8. }
  9. return new DisconfCoreMgrImpl(watchMgr, fetcherMgr, registry);
  10. }
  11. public static WatchMgr getWatchMgr(FetcherMgr fetcherMgr) throws Exception {
  12. synchronized(hostsSync) {
  13. // 从disconf-web端获取 Zoo Hosts信息,及zookeeper host和zk prefix信息(默认 /disconf)
  14. hosts = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooHostsUrl(DisClientSysConfig
  15. .getInstance()
  16. .CONF_SERVER_ZOO_ACTION));
  17. zooPrefix = fetcherMgr.getValueFromServer(DisconfWebPathMgr.getZooPrefixUrl(DisClientSysConfig
  18. .getInstance
  19. ()
  20. .CONF_SERVER_ZOO_ACTION));
  21. /**
  22. * 初始化watchMgr,这里会与zookeeper建立连接,如果/disconf节点不存在会新建
  23. */
  24. WatchMgr watchMgr = new WatchMgrImpl();
  25. watchMgr.init(hosts, zooPrefix, DisClientConfig.getInstance().DEBUG);
  26. return watchMgr;
  27. }
  28. return null;
  29. }
从disconf-web端获取zk host和 zk prefix之后,会建立与zk的连接,然后就该从disconf-web端下载配置和watcher了,也就是disconfCoreMgr.process()逻辑。下载配置时disconf-client从disconf-web端获取配置的全量数据(http连接),并存放到本地,然后解析数据,生成dataMap,dataMap是全量数据。然后将数据注入到仓库中(DisconfCenterStore.confFileMap,类型为Map<String, DisconfCenterFile>)。注意:这里还没有将配置的值设置到bean中,设置bean值是在Spring的finishBeanFactoryInitialization流程做的,准确来说是在初始化bean DisconfMgrBeanSecond(bean配置在disconf.xml中),调用其init方法中做的。
 
在将值设置到仓库之后,就该监听对应配置了,这样才能使用zk的watch机制,在zk上监听的url格式为 /disconf/boot-demo_1_0_0_0_rd/file/specific.properties ,如果该url对应的node不存在则新建,注意该node是persistent类型的。然后在该node下新建临时节点,节点名字是discon-client的签名,格式为host_port_uuid,节点data为针对该配置文件,disconf-client需要的配置项的json格式数据,比如"{"port":9998,"host":"192.168.1.104"}"。
 

1.2 注册DisconfAspectJ

 往Spring中注册一个aspect类DisconfAspectJ,该类会对@DisconfFileItem注解修饰的方法做切面,功能就是当获取bean属性值时,如果开启了DisClientConfig.getInstance().ENABLE_DISCONF,则返回disconf仓库中对应的属性值,否则返回bean实际值。注意:目前版本的disconf在更新仓库中属性值后会将bean的属性值也一同更改,所以,目前DisconfAspectJ类作用已不大,不必理会,关于该类的讨论可参考issue DisconfAspectJ 拦截的作用?

1.3 bean属性注入

bean属性注入是从DisconfMgr.secondScan开始的:

  1. protected synchronized void secondScan() {
  2. // 扫描回调函数,也就是注解@DisconfUpdateService修饰的配置更新回调类,该类需实现IDisconfUpdate
  3. if (scanMgr != null) {
  4. scanMgr.secondScan();
  5. }
  6. // 注入数据至配置实体中
  7. if (disconfCoreMgr != null) {
  8. disconfCoreMgr.inject2DisconfInstance();
  9. }
  10. }
 
bean属性注入通过获取仓库中对应的属性值,然后调用setMethod.invoke或者field.set方法来设置,bean对应的boject是通过Spring来获取的,也就是说,在获取后bean已经初始化完成,只不过对应的属性值还不是配置文件中配置的而已。如果程序中有2个类的@DisconfFile都是同一个配置文件,那么这个时候获取的bean是哪个类的bean呢?关于这个可以点击issue DisconfFile用法咨询,disconf目前只支持一个配置文件一个类的方式,不给两个class同时使用同一个 "resources.properties",否则第二个是不生效的。

 

2 配置动态更新机制

disconf的配置动态更新借助于zk的watch机制(watch机制是zk 3大重要内容之一,其余两个是zk协议和node存储模型)实现的,初始化流程会中会对配置文件注册watch,这样当配置文件更新时,会通知到discnof-client,然后disconf-client再从disconf-web中获取最新的配置并更新到本地,这样就完成了配置动态更新。
 
配置动态更新动作开始于DisconfFileCoreProcessorImpl.updateOneConfAndCallback()方法:
  1. /**
  2. * 更新消息: 某个配置文件 + 回调
  3. */
  4. @Override
  5. public void updateOneConfAndCallback(String key) throws Exception {
  6. // 更新 配置
  7. updateOneConf(key);
  8. // 回调
  9. DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
  10. callUpdatePipeline(key);
  11. }
更新配置时,首先更新仓库中值,然后更新bean属性值,配置更新回调是用户自定义的回调方法,也就是@DisconfUpdateService修饰的类。配置更新时流程是:
开发人员在前端更新配置 -> disconf-web保存数据并更新zookeeper -> zookeeper通知disconf-client -> discnof-client 从 disconf-web下载对应配置 -> 更新仓库和bean属性 -> 调用回调 -> 更新配置完成。
 

小结

disconf 作为一个分布式的配置管理平台,文档详细,易于上手,动态配置更新,满足大多数场景的配置更新需求。美中不足的是,代码有的地方有点臃余,获取配置时还是全量获取方式,目前还不支持多个类共用同一个 "resources.properties",多余的DisconfAspectJ操作等。
 
使用disconf,最好的使用方式是基于它,将其改造成适合本公司或者项目组的工具,比如更方便的注解方式和回调方式等。
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号