文档章节

分布式配置中心disconf第二部(基于注解的实现)

白中墨
 白中墨
发布于 05/12 15:25
字数 2154
阅读 3
收藏 0

开阔下思路,基于我们对spring的理解,如果要完成基于注解实现配置信息的获取,应该做如下3个工作:

  1. 扫描注解,目的是知道有哪些类、哪些属性,需要从分布式配置项、分布式配置文件获取配置,这一步是明确出需求方
  2. 将远程配置项或文件下载到本地,并装载至内存,还需要对远程配置做监听,目的是保证配置数据变更,使用方能够感知,重新去获取
  3. 配置使用,使用一种技术手段在需要查询配置数据的时候,能够把已经装载至本地内存的数据返回

本文从源码进行分析,会涉及一些spring的内部加载机制、反射框架、AOP实现的一些技术点,如下文:

  • 注解扫描
    实现类DisconfMgrBean,完成的是注解扫描,配置应用组件的注入
    这个实现类继承了BeanDefinitionRegistryPostProcessor, PriorityOrdered, ApplicationContextAware,各位技术同学对spring的这三个接口也许并不陌生,下面顺带复习下继承这几个接口的意义
    1、BeanDefinitionRegistryPostProcessor,这个可以实现bean的动态注册,比如传统的定义一个bean的方式,可以使用注解的@Service、@Resources等,这种方式是静态方式的注册、当然使用xml配置一个bean也属于静态的方式,如果实现一种方式根据不同的场景,选择注册不同的bean,可以借助于继承该接口,重写postProcessBeanDefinitionRegistry方法即可
    2、PriorityOrdered,可以定义继承自该接口的bean所对应的优先级,继承该接口也是为了保证DisconfMgrBean能够高优先执行,重写getOrder接口即可,返回值越小,则优先级越高
    3、ApplicationContextAware,spring会扫描所有继承该接口的类,并且执行继承类所重写的setApplicationContext方法,在DisconfMgrBean这里,用户就可以随时使用ApplicationContext托管的各类bean,等做一些需要依赖spring上下文的操作
    注解扫描,指定了扫描的路径scanPackage、以及需要扫描的注解,这里推荐下作者使用的一款reflections的框架,作为类扫描神器:
            <dependency>
                <groupId>org.reflections</groupId>
                <artifactId>reflections</artifactId>
                <version>0.9.9-RC1</version>
            </dependency>

    针对disconf的几种注解,进行扫描的逻辑不进行赘述了,下面是一个大概的代码注解,感兴趣同学可以细看:

        public void firstScan(List<String> packageNameList) throws Exception {
    
            LOGGER.debug("start to scan package: " + packageNameList.toString());
    
            // 获取扫描对象并分析整合(通过注解方式),整合成map:
            //key 含有disconf注解的class,value:该类含有disconfFileItem的方法集合
            //model属性包含:所有包含disConfFile的注解类,所有含disconfFileItem的注解方法,
            //所有含disconfItem的注解方法
            scanModel = scanStaticStrategy.scan(packageNameList);
    
            // 增加非注解的配置
            scanModel.setJustHostFiles(DisconfCenterHostFilesStore.getInstance().getJustHostFiles());
    
            // 放进仓库,通过三类扫描器进行分别装载到对应的仓库(内存)
            for (StaticScannerMgr scannerMgr : staticScannerMgrList) {
    
                // 扫描进入仓库,就是把需求方装入内存
                scannerMgr.scanData2Store(scanModel);
    
                // 忽略哪些KEY
                scannerMgr.exclude(DisClientConfig.getInstance().getIgnoreDisconfKeySet());
            }
        }

    讲完了上面的注解扫描,DisconfMgrBean还有一项功能,就是AOP组件的注册,只有把自定义的查询配置的切面注册至AOP才能达到应用的目的,如下:
     

       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,把AOP的组件动态注册至spring
            registerAspect(registry);
        }
        private void registerAspect(BeanDefinitionRegistry registry) {
    
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DisconfAspectJ.class);
            beanDefinition.setLazyInit(false);
            beanDefinition.setAbstract(false);
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setScope("singleton");
    
            registry.registerBeanDefinition("disconfAspectJ", beanDefinition);
        }

     

  • 配置装载
    需求方明确了,接下来就是需要获取远程配置到本地了,这样距离配置应用更近了一步,看如下逻辑:
                ConfigMgr.init();
    
                LOGGER.info("******************************* DISCONF START FIRST SCAN *******************************");
    
                // registry
                Registry registry = RegistryFactory.getSpringRegistry(applicationContext);
    
                // 扫描器
                scanMgr = ScanFactory.getScanMgr(registry);
    
                // 第一次扫描并入库
                scanMgr.firstScan(scanPackageList);
    
                // 获取数据/注入/Watch,远程数据下载,把配置暂存至内存,增加zk对节点的watch
                disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
                disconfCoreMgr.process();
    

    具体的逻辑比较清晰,1、下载,2、注入至内存,3、增加监听
     

     private void updateOneConfFile(String fileName, DisconfCenterFile disconfCenterFile) throws Exception {
    
            if (disconfCenterFile == null) {
                throw new Exception("cannot find disconfCenterFile " + fileName);
            }
    
            String filePath = fileName;
            Map<String, Object> dataMap = new HashMap<String, Object>();
    
            //
            // 开启disconf才需要远程下载, 否则就本地就好
            //
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
    
                //
                // 下载配置
                //
                try {
    
                    String url = disconfCenterFile.getRemoteServerUrl();
                    filePath = fetcherMgr.downloadFileFromServer(url, fileName, disconfCenterFile.getFileDir());
    
                } catch (Exception e) {
    
                    //
                    // 下载失败了, 尝试使用本地的配置
                    //
    
                    LOGGER.error(e.toString(), e);
                    LOGGER.warn("using local properties in class path: " + fileName);
    
                    // change file path
                    filePath = fileName;
                }
                LOGGER.debug("download ok.");
            }
    
            try {
                dataMap = FileTypeProcessorUtils.getKvMap(disconfCenterFile.getSupportFileTypeEnum(),
                        disconfCenterFile.getFilePath());
            } catch (Exception e) {
                LOGGER.error("cannot get kv data for " + filePath, e);
            }
    
            //
            // 注入到仓库中
            //
            disconfStoreProcessor.inject2Store(fileName, new DisconfValue(null, dataMap));
            LOGGER.debug("inject ok.");
    
            //
            // 开启disconf才需要进行watch
            //
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
                //
                // Watch
                //
                DisConfCommonModel disConfCommonModel = disconfStoreProcessor.getCommonModel(fileName);
                if (watchMgr != null) {
                    watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                            GsonUtils.toJson(disconfCenterFile.getKV()));
                    LOGGER.debug("watch ok.");
                } else {
                    LOGGER.warn("cannot monitor {} because watch mgr is null", fileName);
                }
            }
        }

     

  • 配置应用
    这里大概介绍下,所有需要从远程获取的数据,都能够保证,有且正确的数据:
    1、DiconfMgr的secondScan方法,第二次扫描会做两件事,第一是扫描需要做配置更新回调的类,写入内存,第二根据本地内存仓库的配置,对实体做配置的注入:
        public void secondScan() throws Exception {
    
            // 开启disconf才需要处理回调
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
    
                if (scanModel == null) {
                    synchronized(scanModel) {
                        // 下载模块必须先初始化
                        if (scanModel == null) {
                            throw new Exception("You should run first scan before second Scan");
                        }
                    }
                }
    
                // 操作更新回调的实现类,将回调函数实例化并写入仓库
                ScanDynamicStoreAdapter.scanUpdateCallbacks(scanModel, registry);
            }
        }
        //这是实体属性注入的方法
        private void inject2OneConf(String fileName, DisconfCenterFile disconfCenterFile) {
    
            if (disconfCenterFile == null) {
                return;
            }
    
            try {
    
                //
                // 获取实例
                //
    
                Object object;
                try {
    
                    object = disconfCenterFile.getObject();
                    if (object == null) {
                        object = registry.getFirstByType(disconfCenterFile.getCls(), false, true);
                    }
    
                } catch (Exception e) {
    
                    LOGGER.error(e.toString());
                    object = null;
                }
    
                // 注入实体中
                disconfStoreProcessor.inject2Instance(object, fileName);
    
            } catch (Exception e) {
                LOGGER.warn(e.toString(), e);
            }
        }
    //详细的注入逻辑
    public void inject2Instance(Object object, String fileName) {
    
            DisconfCenterFile disconfCenterFile = getInstance().getConfFileMap().get(fileName);
    
            // 校验是否存在
            if (disconfCenterFile == null) {
                LOGGER.error("cannot find " + fileName + " in store....");
                return;
            }
    
            //
            // 静态类
            //
            if (object != null) {
                // 设置object
                disconfCenterFile.setObject(object);
            }
    
            // 根据类型设置值
            //
            // 注入实体
            //
            Map<String, FileItemValue> keMap = disconfCenterFile.getKeyMaps();
            for (String fileItem : keMap.keySet()) {
    
                // 根据类型设置值
                try {
    
                    //
                    // 静态类
                    //
                    if (object == null) {
    
                        if (keMap.get(fileItem).isStatic()) {
                            LOGGER.debug(fileItem + " is a static field. ");
                            keMap.get(fileItem).setValue4StaticFileItem(keMap.get(fileItem).getValue());
                        }
    
                        //
                        // 非静态类
                        //
                    } else {
    
                        LOGGER.debug(fileItem + " is a non-static field. ");
    
                        if (keMap.get(fileItem).getValue() == null) {
    
                            // 如果仓库值为空,则实例 直接使用默认值
                            Object defaultValue = keMap.get(fileItem).getFieldDefaultValue(object);
                            keMap.get(fileItem).setValue(defaultValue);
    
                        } else {
    
                            // 如果仓库里的值为非空,则实例使用仓库里的值
                            keMap.get(fileItem).setValue4FileItem(object, keMap.get(fileItem).getValue());
                        }
                    }
    
                } catch (Exception e) {
                    LOGGER.error("inject2Instance fileName " + fileName + " " + e.toString(), e);
                }
            }
        }

    2、AOP保证每次请求都会从配置仓库获取最新的配置
    涉及到两类注解一类是含有disconfFileItem注解的所有方法,一类是含有disconfItem注解的所有方法,实现的类是DisconfAspectJ,如下:
    @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远程才会进行切面
         *
         * @throws Throwable
         */
        @Around("anyPublicMethod() && @annotation(disconfItem)")
        public Object decideAccess(ProceedingJoinPoint pjp, DisconfItem disconfItem) throws Throwable {
    
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
                //
                // 请求仓库配置数据
                //
                DisconfStoreProcessor disconfStoreProcessor = DisconfStoreProcessorFactory.getDisconfStoreItemProcessor();
                Object ret = disconfStoreProcessor.getConfig(null, disconfItem.key());
                if (ret != null) {
                    LOGGER.debug("using disconf store value: (" + disconfItem.key() + " , " + ret + ")");
                    return ret;
                }
            }
    
            Object rtnOb;
    
            try {
                // 返回原值
                rtnOb = pjp.proceed();
            } catch (Throwable t) {
                LOGGER.info(t.getMessage());
                throw t;
            }
    
            return rtnOb;
        }
    }

     

  • 配置更新
    实现逻辑在NodeWatcher类中,前面已经介绍过在第一次扫描时已经创建了监听,触发监听以后,会重新装载内存数据+调用更新回调类,监听事件,当event.getType() == EventType.NodeDataChanged会调用callback,应该是根据文件做的监听,见下文:
        private void callback() {
    
            try {
    
                // 调用回调函数, 回调函数里会重新进行监控
                try {
                    disconfSysUpdateCallback.reload(disconfCoreMgr, disConfigTypeEnum, keyName);
                } catch (Exception e) {
                    LOGGER.error(e.toString(), e);
                }
    
            } catch (Exception e) {
    
                LOGGER.error("monitor node exception. " + monitorPath, e);
            }
        }
    //具体的更新配置和回调执行
        @Override
        public void updateOneConfAndCallback(String key) throws Exception {
    
            // 更新 配置
            updateOneConf(key);
    
            // 回调
            DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
            callUpdatePipeline(key);
        }

     

© 著作权归作者所有

白中墨
粉丝 1
博文 27
码字总数 41497
作品 0
昌平
私信 提问
分布式配置管理平台--Disconf

Distributed Configuration Management Platform(分布式配置管理平台) 专注于各种 分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务。 包括 百度、滴滴打车、银联、网易、拉...

knightliao
2015/04/13
80.5K
21
分布式配置管理平台Disconf

摘要 为了更好的解决分布式环境下多台服务实例的配置统一管理问题,本文提出了一套完整的分布式配置管理解决方案(简称为disconf[4],下同)。首先,实现了同构系统的配置发布统一化,提供了...

很好亦平凡ms
2016/07/13
165
0
SpringCloud实战7-Config分布式配置管理

分布式环境下的统一配置框架,已经有不少了,比如百度的disconf,阿里的diamand 官方文档对spring Cloud Config的描述如下:   Spring Cloud Config为分布式系统中的外部配置提供服务器和客...

狂小白
2018/05/21
0
0
Spring Cloud 如何选择分布式配置中心

微服务必备的几样武器有了,才能独闯武林, 有哪几样呢? 注册中心(eureka, consul, zk, etcd) 配置中心 (Spring Cloud Config, disconf ) API网关 (Spring Cloud zuul, kong) 熔断器 (hys...

尹吉欢
2017/11/30
0
0
分布式配置管理平台VS统一集中配置管理

在大型集群和分布式应用中,配置不宜分散到节点中,应该集中管理,为各种业务平台提供统一的配置管理服务。 随着业务的发展,应用系统中的配置通常会越来越多,常见的一些应用配置大致会有数...

English0523
2017/12/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

计算机实现原理专题--二进制减法器(二)

在计算机实现原理专题--二进制减法器(一)中说明了基本原理,现准备说明如何来实现。 首先第一步255-b运算相当于对b进行按位取反,因此可将8个非门组成如下图的形式: 由于每次做减法时,我...

FAT_mt
今天
5
0
好程序员大数据学习路线分享函数+map映射+元祖

好程序员大数据学习路线分享函数+map映射+元祖,大数据各个平台上的语言实现 hadoop 由java实现,2003年至今,三大块:数据处理,数据存储,数据计算 存储: hbase --> 数据成表 处理: hive --> 数...

好程序员官方
今天
7
0
tabel 中含有复选框的列 数据理解

1、el-ui中实现某一列为复选框 实现多选非常简单: 手动添加一个el-table-column,设type属性为selction即可; 2、@selection-change事件:选项发生勾选状态变化时触发该事件 <el-table @sel...

everthing
今天
6
0
【技术分享】TestFlight测试的流程文档

上架基本需求资料 1、苹果开发者账号(如还没账号先申请-苹果开发者账号申请教程) 2、开发好的APP 通过本篇教程,可以学习到ios证书申请和打包ipa上传到appstoreconnect.apple.com进行TestF...

qtb999
今天
10
0
再见 Spring Boot 1.X,Spring Boot 2.X 走向舞台中心

2019年8月6日,Spring 官方在其博客宣布,Spring Boot 1.x 停止维护,Spring Boot 1.x 生命周期正式结束。 其实早在2018年7月30号,Spring 官方就已经在博客进行过预告,Spring Boot 1.X 将维...

Java技术剑
今天
18
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部