天天看點

disconf-client原理分析

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

流程圖為:

disconf-client原理分析

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 *******************************");
    }           

複制

disconf-client原理分析

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,則優先從倉庫擷取配置資料,否則從實體類中獲得。

流程總結

disconf-client原理分析

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無法攔截得到,這樣就無法統一處理這些配置。一旦出現這種情況,“非一緻性讀問題”就會産生。