前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >disconf-client原理分析

disconf-client原理分析

作者头像
Monica2333
发布2020-06-22 11:59:30
5130
发布2020-06-22 11:59:30
举报
文章被收录于专栏:码农知识点码农知识点

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

  • scan: 配置扫描模块
  • core: 配置核心处理模块
  • fetch: 配置抓取模块
  • watch: 配置监控模块
  • store: 配置仓库模块
  • addons: 配置reload模块

启动 在disconf.xml中的定义如下:

代码语言:javascript
复制
  <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

代码语言:javascript
复制
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)所做的事情为:

代码语言:javascript
复制
  /**
     * 第一次扫描,静态扫描 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:主要是加载调用回调函数,进行配置的后续动态处理

代码语言:javascript
复制
public class DisconfMgrBeanSecond {

    public void init() {

        DisconfMgr.getInstance().secondScan();
    }

DisconfMgr的secondScan

代码语言:javascript
复制
/**
     * 第二次扫描, 动态扫描, 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收到事件通知时,会调用本地回调函数,业务逻辑会回调至此

代码语言:javascript
复制
/**
 * 当配置更新时,系统会自动 调用此回调函数<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()

代码语言:javascript
复制
 /**
     * 更新消息: 某个配置文件 + 回调,同时会再次在zk节点上注册watch
     */
    @Override
    public void updateOneConfAndCallback(String key) throws Exception {

        // 更新 配置,从disconf-web重新下载数据,并更新本地仓库和配置类实例,开启disconf才需要进行watch
        updateOneConf(key);

        // 配置文件回调类
        DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
      //通用回调类,需实现IDisconfUpdatePipeline接口
        callUpdatePipeline(key);
    }

配置数据的获取

代码语言:javascript
复制
@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无法拦截得到,这样就无法统一处理这些配置。一旦出现这种情况,“非一致性读问题”就会产生。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档