disconf原理 “入坑”指南
之前有了解过disconf,也知道它是基于zookeeper来做的,但是对于其运行原理不太了解,趁着周末,debug下源码,也算是不枉费周末大好时光哈 :) 。关于这篇文章,笔者主要是参考disconf源码和官方文档,若有不正确地方,感谢评论区指正交流~
disconf是一个分布式配置管理平台(distributed configuration management platform),专注于各种 分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务,是一套完整的基于zookeeper的分布式配置统一解决方案。disconf目前已经被多个公司在使用,包括百度、滴滴出行、银联、网易、拉勾网、苏宁易购、顺丰科技 等知名互联网公司。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引入依赖即可:
<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。
1 disconf-client 初始化流程
<!-- 使用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属性注入。
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); }
1.1 firstscan
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获取某些符合条件的资源列表,如下:
/** * 扫描基本信息 */ 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; }
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开始的:
protected synchronized void secondscan() { // 扫描回调函数,也就是注解@disconfupdateservice修饰的配置更新回调类,该类需实现idisconfupdate if (scanmgr != null) { scanmgr.secondscan(); } // 注入数据至配置实体中 if (disconfcoremgr != null) { disconfcoremgr.inject2disconfinstance(); } }
2 配置动态更新机制
/** * 更新消息: 某个配置文件 + 回调 */ @override public void updateoneconfandcallback(string key) throws exception { // 更新 配置 updateoneconf(key); // 回调 disconfcoreprocessutils.calloneconf(disconfstoreprocessor, key); callupdatepipeline(key); }
小结
上一篇: 不能把有些人叫穷人
下一篇: Android中SQLite的使用讲解