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上的結點,如果節點不存在,用戶端會自己建立節點
流程圖為:

client第一次掃描流程圖.jpg
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,則優先從倉庫擷取配置資料,否則從實體類中獲得。
流程總結
client端流程示意圖
啟動事件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無法攔截得到,這樣就無法統一處理這些配置。一旦出現這種情況,“非一緻性讀問題”就會産生。