disconf-client原理分析

2019-01-07  本文已影响0人  Monica2333

disconf-client各个模块的作用如下:

启动
在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实现了BeanFactoryPostProcessor和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:(以下按顺序进行)

注意事项:

上一篇 下一篇

猜你喜欢

热点阅读