美文网首页
disconf-client原理分析

disconf-client原理分析

作者: Monica2333 | 来源:发表于2019-01-07 18:19 被阅读0次

    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实现了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:(以下按顺序进行)
    • 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无法拦截得到,这样就无法统一处理这些配置。一旦出现这种情况,“非一致性读问题”就会产生。

    相关文章

      网友评论

          本文标题:disconf-client原理分析

          本文链接:https://www.haomeiwen.com/subject/vldurqtx.html