disconf-client各个模块的作用如下:
- scan: 配置扫描模块
- core: 配置核心处理模块
- fetch: 配置抓取模块
- watch: 配置监控模块
- store: 配置仓库模块
- addons: 配置reload模块
启动
在disconf.xml中的定义如下:
<context:component-scan base-package="com.globalegrow.esearch.search.disconf"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
destroy-method="destroy">
<!--扫描的包路径-->
<property name="scanPackage" value="com.globalegrow.esearch.search.disconf"/>
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
init-method="init" destroy-method="destroy">
</bean>
com.baidu.disconf.client.DisconfMgrBean
:第一个加载的bean
public class DisconfMgrBean implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ApplicationContextAware {
private ApplicationContext applicationContext;
private String scanPackage = null;
public void destroy() {
DisconfMgr.getInstance().close();
}
public void setScanPackage(String scanPackage) {
this.scanPackage = scanPackage;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
/**
* 第一次扫描<br/>
* 在Spring内部的Bean定义初始化后执行,这样是最高优先级的
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 为了做兼容
DisconfCenterHostFilesStore.getInstance().addJustHostFileSet(fileList);
List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);
// unique
Set<String> hs = new HashSet<String>();
hs.addAll(scanPackList);
scanPackList.clear();
scanPackList.addAll(hs);
// 进行扫描
DisconfMgr.getInstance().setApplicationContext(applicationContext);
DisconfMgr.getInstance().firstScan(scanPackList);
// register java bean disconfAspectJ,用于注解AOP的拦截
registerAspect(registry);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
此Bean实现了BeanFactoryPostProcesso
r和PriorityOrdered
接口。它的Bean初始化Order是最高优先级的。
因此,当Spring扫描了所有的Bean信息后,在所有Bean初始化(init)之前,DisconfMgrBean的postProcessBeanFactory
方法将被调用,在这里,Disconf-Client会进行第一次扫描。
DisconfMgr的firstScan(scanPackList)所做的事情为:
/**
* 第一次扫描,静态扫描 for annotation config
*/
protected synchronized void firstScan(List<String> scanPackageList) {
// 该函数不能调用两次
if (isFirstInit) {
LOGGER.info("DisConfMgr has been init, ignore........");
return;
}
try {
// 1.导入配置,初始化disconf.properties系统配置
ConfigMgr.init();
LOGGER.info("******************************* DISCONF START FIRST SCAN *******************************");
// registry
Registry registry = RegistryFactory.getSpringRegistry(applicationContext);
// 扫描器
scanMgr = ScanFactory.getScanMgr(registry);
// 第一次扫描并入库,主要是解析出配置类和配置文件的信息
scanMgr.firstScan(scanPackageList);
// 获取数据/注入/Watch
disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
/**
* (第一次扫描时使用)<br/>
* 1. 获取远程的所有配置数据
* 2. 注入到仓库中
* 3. Watch 配置
*/
disconfCoreMgr.process();
isFirstInit = true;
LOGGER.info("******************************* DISCONF END FIRST SCAN *******************************");
} catch (Exception e) {
LOGGER.error(e.toString(), e);
}
}
扫描按顺序做了以下几个事情:
1.初始化Disconf-client自己的配置模块。
2.初始化Scan模块。
3.初始化Core模块,并极联初始化Watch,Fetcher,Restful模块。
4.扫描用户类,整合分布式配置注解相关的静态类信息至配置仓库里。
5.执行Core模块,从disconf-web平台上下载配置数据:配置文件下载到本地,配置项直接下载。
6.配置文件和配置项的数据会注入到配置仓库里。
7.使用watch模块为所有配置关联ZK上的结点,如果节点不存在,客户端会自己创建节点
流程图为:
DisconfMgrBeanSecond
:主要是加载调用回调函数,进行配置的后续动态处理
public class DisconfMgrBeanSecond {
public void init() {
DisconfMgr.getInstance().secondScan();
}
DisconfMgr的secondScan
/**
* 第二次扫描, 动态扫描, for annotation config
*/
protected synchronized void secondScan() {
// 该函数必须第一次运行后才能运行
if (!isFirstInit) {
LOGGER.info("should run First Scan before Second Scan.");
return;
}
// 第二次扫描也只能做一次
if (isSecondInit) {
LOGGER.info("should not run twice.");
return;
}
LOGGER.info("******************************* DISCONF START SECOND SCAN *******************************");
try {
// 扫描回调函数并保存到仓库,用于更新配置时候的调用
if (scanMgr != null) {
scanMgr.secondScan();
}
// 注入数据至配置实体中
if (disconfCoreMgr != null) {
disconfCoreMgr.inject2DisconfInstance();
}
} catch (Exception e) {
LOGGER.error(e.toString(), e);
}
isSecondInit = true;
//
// 不开启 则不要打印变量map
//
if (DisClientConfig.getInstance().ENABLE_DISCONF) {
//
String data = DisconfStoreProcessorFactory.getDisconfStoreFileProcessor()
.confToString();
if (!StringUtils.isEmpty(data)) {
LOGGER.info("Conf File Map: {}", data);
}
//
data = DisconfStoreProcessorFactory.getDisconfStoreItemProcessor()
.confToString();
if (!StringUtils.isEmpty(data)) {
LOGGER.info("Conf Item Map: {}", data);
}
}
LOGGER.info("******************************* DISCONF END *******************************");
}
client第二次扫描流程图.jpg
配置文件更新
如果disconf-web更新配置文件时,zk-client收到事件通知时,会调用本地回调函数,业务逻辑会回调至此
/**
* 当配置更新时,系统会自动 调用此回调函数<br/>
* 这个函数是系统调用的,当有配置更新时,便会进行回调
*
* @author liaoqiqi
* @version 2014-5-16
*/
public class DisconfSysUpdateCallback implements IDisconfSysUpdate {
/**
*
*/
@Override
public void reload(DisconfCoreProcessor disconfCoreMgr, DisConfigTypeEnum disConfigTypeEnum, String keyName)
throws Exception {
// 更新配置数据仓库 && 调用用户的回调函数列表
disconfCoreMgr.updateOneConfAndCallback(keyName);
}
}
比如配置文件的更新,DisconfFileCoreProcessorImpl.updateOneConfAndCallback()
/**
* 更新消息: 某个配置文件 + 回调,同时会再次在zk节点上注册watch
*/
@Override
public void updateOneConfAndCallback(String key) throws Exception {
// 更新 配置,从disconf-web重新下载数据,并更新本地仓库和配置类实例,开启disconf才需要进行watch
updateOneConf(key);
// 配置文件回调类
DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
//通用回调类,需实现IDisconfUpdatePipeline接口
callUpdatePipeline(key);
}
配置数据的获取
@Aspect
public class DisconfAspectJ {
protected static final Logger LOGGER = LoggerFactory.getLogger(DisconfAspectJ.class);
@Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}
/**
* 获取配置文件数据, 只有开启disconf远程才会进行切面
*
* @throws Throwable
*/
@Around("anyPublicMethod() && @annotation(disconfFileItem)")
public Object decideAccess(ProceedingJoinPoint pjp, DisconfFileItem disconfFileItem) throws Throwable {
if (DisClientConfig.getInstance().ENABLE_DISCONF) {
MethodSignature ms = (MethodSignature) pjp.getSignature();
Method method = ms.getMethod();
//
// 文件名
//
Class<?> cls = method.getDeclaringClass();
DisconfFile disconfFile = cls.getAnnotation(DisconfFile.class);
//
// Field名
//
Field field = MethodUtils.getFieldFromMethod(method, cls.getDeclaredFields(), DisConfigTypeEnum.FILE);
if (field != null) {
//
// 请求仓库配置数据
//
DisconfStoreProcessor disconfStoreProcessor =
DisconfStoreProcessorFactory.getDisconfStoreFileProcessor();
Object ret = disconfStoreProcessor.getConfig(disconfFile.filename(), disconfFileItem.name());
if (ret != null) {
LOGGER.debug("using disconf store value: " + disconfFile.filename() + " ("
+ disconfFileItem.name() +
" , " + ret + ")");
return ret;
}
}
}
Object rtnOb;
try {
// 返回原值
rtnOb = pjp.proceed();
} catch (Throwable t) {
LOGGER.info(t.getMessage());
throw t;
}
return rtnOb;
}
}
如果开启了远程disconf.enable.remote.conf=true,则优先从仓库获取配置数据,否则从实体类中获得。
流程总结
启动事件A:
(以下按顺序进行)
- A1:扫描静态注解类数据,并注入到配置仓库里。
- A2:根据仓库里的配置文件、配置项,到 disconf-web 平台里下载配置数据。
- A3:将下载得到的配置数据值注入到仓库里。
- A4:根据仓库里的配置文件、配置项,去ZK上监控结点。
- A5:根据XML配置定义,到 disconf-web 平台里下载配置文件,放在仓库里,并监控ZK结点。
- A6:A1-A5均是处理静态类数据。A6是处理动态类数据,包括:实例化配置的回调函数类;将配置的值注入到配置实体里。
更新配置事件B:
- B1:管理员在 Disconf-web 平台上更新配置。
- B2:Disconf-web 平台发送配置更新消息给ZK指定的结点。
- B3:ZK通知 Disconf-cient 模块。
- B4:与A2一样。唯一不同的是它只处理一个配置文件或者一个配置项,而事件A2则是处理所有配置文件和配置项。下同。
- B5:与A3一样。
- B6:基本与A4一样,区别是,这里还会将配置的新值注入到配置实体里。
注意事项:
- 配置文件类、配置项所在的类、回调函数类 都必须是JavaBean,并且它们的”scope” 都必须是singleton的。
- 本系统实现的注解方案具有些局限性,具体如下:
- 用户标注配置时略有些不习惯。目前注解是放在get方法之上的,而不是放在域上。
- 注解放在get方法上,一般情况下是没有问题的。但是对于”call self”的方法调用,AOP无法拦截得到,这样就无法统一处理这些配置。一旦出现这种情况,“非一致性读问题”就会产生。
网友评论