专栏首页码农知识点disconf-client原理分析

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • disconf客户端使用篇

    此时,当更新disconf-web管理台对应的remote.properties文件时,会重新在disconf/config目录下下载最新文件,RemoteSe...

    Monica2333
  • disconf架构设计

    disconf是基于zookeeper watch机制的分布式配置统一解决方案。 特点

    Monica2333
  • Java NIO实现原理之Selector

    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个cha...

    Monica2333
  • springboot 国际化

    听起来高大上的国际化,起始就是在利用浏览器语言,或者页面中的中英文切换,将页面的文字在其他语言和中文进行切换,比如:

    DencyCheng
  • Hexo+Github配置与主题

    每个主题都会配置几种界面显示语言,修改语言只要编辑站点配置文件,找到 language 字段,并将其值更改为你所需要的语言(例如,简体中文):

    smartsi
  • spring boot2.0.0 redis序列化解决方案

    24-丰总
  • 重学数据结构(二、栈)

    比如上面的羽毛球筒,只能将最顶端的羽毛球移出,也只能将新的羽毛球放到最顶端——这两种操作分别称作入栈( Push)和出栈( Pop)。入栈和出栈的示意图如下:

    三分恶
  • java应用CAS

      CAS(Compare and Swap),即比较并替换。jdk里的大量源码通过CAS来提供线程安全操作,比如AtomicInteger类。下面我们来分析一...

    良辰美景TT
  • 进展:外雨带驱动热带气旋次眼墙形成的机制研究

    过去几十年,热带气旋的路径预报取得了长足进展。然而,热带气旋的强度,包括快速增强/减弱、强度震荡等,依然是预报中的难点。其中,次眼墙形成和眼墙替换是造成热带气旋...

    气象学家
  • IDEA: 遇到问题Error during artifact deployment. See server log for details解决方法

    Dar_Alpha

扫码关注云+社区

领取腾讯云代金券