Disconf (version : 2.6.21)
通常我们会做如下配置:(disconf 2.6.21)
<!-- 一次扫描 --> <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean" destroy-method="destory"> <property name="scanPackage" value="com.cn.biz.config" /> </bean> <!-- 二次扫描 --> <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond" init-method="init" destroy-method="destory"> </bean>
一次扫描:
0. ConfigMgr.init();初始化disconf-client端基础配置。
disconf_sys.properties注入到DisClientSysConfig (文件下载路径等等.....)
disconf.properties注入到DisClientConfig (环境信息等等)
1. 静态解析:解析scanPackage包下面的java类,将client端的Disconf pojo对象利用反射进行解析(Reflections工具包),组装成数据结构ScanStaticModel
/** * 扫描静态存储的对象 */ public class ScanStaticModel { private Reflections reflections; // 所有的@DisconfFile标记的类 private Set<Class<?>> disconfFileClassSet; // 所有的@DisconfFileItem标记的method private Set<Method> disconfFileItemMethodSet; // @DisconfFile与@DisconfFileItem对应关系的Map private Map<Class<?>, Set<Method>> disconfFileItemMap; // 配置ITEM private Set<Method> disconfItemMethodSet; // 主从切换的回调函数类 private Set<Class<?>> disconfActiveBackupServiceClassSet; // 更新 回调函数类 private Set<Class<?>> disconfUpdateService; .......... }
2. 根据基础配置,将静态解析的ScanStaticModel转换成DisconfCenterFile,为从disconf-web端下载配置文件做准备
3. 连接Zookeeper,使用ConnectionWatcher监控连接事件
4. 根据DisconfCenterFile中的信息,从disconf-web端下载properties文件,解析并存放到DisconfCenterFile.keyMaps <key:配置项名称, value:配置值>
同时,对文件进行监控,变更后通知到NodeWatcher。(NodeWatcher触发时,会更新DisconfCenterFile.keyMaps,并将值重新注入到Disconf pojo属性中),与二次扫描时的第2点类似
------------------------------------
Spring容器启动,注入了切面DisconfAspectJ,所有的带有@DisconfFileItem的public方法,都会被拦截,取DisconfCenterFile.keyMaps中取值(也就是从disconf-web端下载properties文件中取值)进行返回
------------------------------------
二次扫描:
1. 注入回调方法(IDisconfUpdate的实现类)
2. 用一次扫描中DisconfCenterFile.keyMaps中的值,将Disconf pojo属性进行填充赋值,即注入数据至配置实体中
整体感觉数据结构比较混乱
既然有了切面DisconfAspectJ,二次扫描中将Disconf pojo属性进行填充赋值显得没有什么用。
确实,经过测试,去掉二次扫描的Bean DisconfMgrBeanSecond配置,也能够达到配置更改通知的效果。唯一缺少的就是配置更新后的IDisconfUpdate回调
感觉一二次扫描合成一次扫描,配置成一个Spring Bean,二次扫描的功能在bean上配置成一个开关
Disconf NodeWatcher是使用ZooKeeper.getData(String path, Watcher watcher, Stat stat)来实现的,ZooKeeper Watch事件是一个一次性的触发器,Watcher通知后每次又要注册一个新的NodeWatcher。不知有没有永久监听的方案?
这个可以用zkclient.jar curator.jar来做一直监听
--------------------------
disconf 2.6.36:
DisconfDataGetter.java : 获取配置项对应的值的API
下载文件时,使用到了文件锁--FileOutputStream.getChannel().tryLock();:
com.baidu.disconf.core.common.utils.OsUtil#transferFileAtom()
1 public static void transferFileAtom(File src, File dest, boolean isDeleteSource) throws Exception { 2 3 // 文件锁所在文件 4 File lockFile = new File(dest + ".lock"); 5 FileOutputStream outStream = null; 6 FileLock lock = null; 7 8 try { 9 10 int tryTime = 0; 11 while (tryTime < 3) { 12 13 try { 14 15 outStream = new FileOutputStream(lockFile); 16 FileChannel channel = outStream.getChannel(); 17 18 lock = channel.tryLock(); 19 if (lock != null) { 20 21 if (dest.exists()) { 22 // 判断内容是否一样 23 if (FileUtils.isFileEqual(src, dest)) { 24 // 内容如果一样,就只需要删除源文件就行了 25 if (isDeleteSource) { 26 src.delete(); 27 } 28 break; 29 } 30 } 31 32 logger.debug("start to replace " + src.getAbsolutePath() + " to " + dest.getAbsolutePath()); 33 34 // 转移 35 transferFile(src, dest); 36 37 // 删除源文件 38 if (isDeleteSource) { 39 src.delete(); 40 } 41 42 break; 43 } 44 45 } catch (FileNotFoundException e) { 46 47 // 打不开文件,则后面进行重试 48 logger.warn(e.toString()); 49 50 } finally { 51 52 // 释放锁,通道;删除锁文件 53 if (null != lock) { 54 try { 55 lock.release(); 56 } catch (IOException e) { 57 logger.warn(e.toString()); 58 } 59 60 } 61 62 if (outStream != null) { 63 try { 64 outStream.close(); 65 } catch (IOException e) { 66 logger.warn(e.toString()); 67 } 68 } 69 70 if (lockFile != null) { 71 lockFile.delete(); 72 } 73 74 } 75 76 // 进行重试 77 logger.warn("try lock failed. sleep and try " + tryTime); 78 tryTime++; 79 80 try { 81 Thread.sleep(1000 * tryTime); 82 } catch (Exception e) { 83 System.out.print(""); 84 } 85 86 } 87 88 } catch (IOException e) { 89 logger.warn(e.toString()); 90 } 91 92 }