一.概述

1.什么是配置

应用程序在启动和运行的时候往往需要读取一些配置信息,配置基本上伴随着应用程序的整个生命周期,比如:数据库连接参数、启动参数等。

配置主要有以下几个特点:

  • 配置是独立于程序的只读变量

    • 配置首先是独立于程序的,同一份程序在不同的配置下会有不同的行为

    • 其次,配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置

  • 配置伴随应用的整个生命周期

    • 配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。 比如:启动时需要读取服务的端口号、系统在运行过程中需要读取定时策略执行定时任务等。

  • 配置可以有多种加载方式

    • 常见的有程序内部硬编码,配置文件,环境变量,启动参数,基于数据库等

  • 配置需要治理

    • 权限控制:由于配置能改变程序的行为,不正确的配置甚至能引起灾难,所以对配置的修改必须有比较完善的权限控制

    • 不同环境、集群配置管理:同一份程序在不同的环境(开发,测试,生产)、不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境、集群配置管理

2.什么是配置中心

传统单体应用存在一些潜在缺陷,如随着规模的扩大,部署效率降低,团队协作效率差,系统可靠性变差,维护困难,新功能上线周期长等,所以迫切需要一种新的架构去解决这些问题,而微服务架构正是当下一种流行的解法。

不过,解决一个问题的同时,往往会诞生出很多新的问题,所以微服务化的过程中伴随着很多的挑战,其中一个挑战就是有关服务(应用)配置的。当系统从一个单体应用,被拆分成分布式系统上一个个服务节点后,配置文件也必须跟着迁移(分割),这样配置就分散了,不仅如此,分散中还包含着冗余,如下图:

配置中心将配置从应用中剥离出来,统一管理,优雅的解决了配置的动态变更、持久化、运维成本等问题。

应用自身既不需要去添加管理配置接口,也不需要自己去实现配置的持久化,更不需要引入“定时任务”以便降低运维成本。

总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。

在系统架构中,配置中心是整个微服务基础架构体系中的一个组件,如下图,它的功能看上去并不起眼,无非就是配置的管理和存取,但它是整个微服务架构中不可或缺的一环。

集中管理配置,那么就要将应用的配置作为一个单独的服务抽离出来了,同理也需要解决新的问题,比如:版本管理(为了支持回滚),权限管理等。

总结一下,在传统巨型单体应用纷纷转向细粒度微服务架构的历史进程中,配置中心是微服务化不可缺少的一个系统组件,在这种背景下中心化的配置服务即配置中心应运而生,一个合格的配置中心需要满足:

  • 配置项容易读取和修改

  • 添加新配置简单直接

  • 支持对配置的修改的检视以把控风险

  • 可以查看配置修改的历史记录

  • 不同部署环境支持隔离

二.disconf介绍

1.基本介绍

  • 支持配置(配置项/配置文件)分布式管理

  • 配置发布统一化

    • 配置发布、更新统一化,同一个上线包 无须改动配置 即可在 多个环境中(RD/QA/PRODUCTION) 上线

    • 配置更新自动化:用户在平台更新配置,使用该配置的系统会自动发现该情况,并应用新配置。特殊地,如果用户为此配置定义了回调函数类,则此函数类会被自动调用

  • 上手简单,基于注解或者xml配置方式

2.架构图

3.依赖

使用disconf,只需要在pom中引入依赖

<dependency><groupId>com.baidu.disconf</groupId><artifactId>disconf-client</artifactId><version>2.6.36</version>
</dependency>

4.disconf初始化流程

关于disconf-client的初始化,联想到Spring IoC流程,我们先不看代码,可以猜想一下其大致流程,disconf-client首先需要从disconf服务端获取配置,然后等到IoC流程中创建好对应的bean之后,将对应的配置值设置到bean中,这样基本上就完成了初始化流程,其实disconf的初始化实现就是这样的。

disconf-client的初始化开始于BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(Spring IoC初始化时,对于BeanDefinitionRegistryPostProcessor的实现类,会调用其postProcessBeanDefinitionRegistry方法),disconf的DisconfMgrBean类就是BeanDefinitionRegistryPostProcessor的实现类,DisconfMgrBean类的bean配置在哪里呢?其实就是disconf.xml中的配置,该配置是必须的,示例如下:

<!-- 使用disconf必须添加以下配置 -->
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"destroy-method="destroy"><property name="scanPackage" value="com.yfy.test"/>
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"init-method="init" destroy-method="destroy">
</bean>

DisconfMgrBean 的 postProcessBeanDefinitionRegistry 方法主要做的3件事就是扫描(firstScan)、注册DisconfAspectJ 和 bean属性注入。

    /*** 第一次扫描<br/>* 在Spring内部的Bean定义初始化后执行,这样是最高优先级的*/@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
​// 为了做兼容DisconfCenterHostFilesStore.getInstance().addJustHostFileSet(fileList);
​List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);// uniqueSet<String> hs = new HashSet<String>();hs.addAll(scanPackList);scanPackList.clear();scanPackList.addAll(hs);
​// 进行扫描DisconfMgr.getInstance().setApplicationContext(applicationContext);DisconfMgr.getInstance().firstScan(scanPackList);
​// register java beanregisterAspect(registry);}

进行包扫描是使用Reflections来完成的,获取路径下(比如xxx/target/classes)某个包下符合条件(比如com.yfy.test)的资源(reflections),然后从reflections获取某些符合条件的资源列表,如下

 /*** 扫描基本信息*/private ScanStaticModel scanBasicInfo(List<String> packNameList) {
​ScanStaticModel scanModel = new ScanStaticModel();
​//// 扫描对象//Reflections reflections = getReflection(packNameList);scanModel.setReflections(reflections);
​//// 获取DisconfFile class//Set<Class<?>> classdata = reflections.getTypesAnnotatedWith(DisconfFile.class);scanModel.setDisconfFileClassSet(classdata);
​//// 获取DisconfFileItem method//Set<Method> af1 = reflections.getMethodsAnnotatedWith(DisconfFileItem.class);scanModel.setDisconfFileItemMethodSet(af1);
​//// 获取DisconfItem method//af1 = reflections.getMethodsAnnotatedWith(DisconfItem.class);scanModel.setDisconfItemMethodSet(af1);
​//// 获取DisconfActiveBackupService//classdata = reflections.getTypesAnnotatedWith(DisconfActiveBackupService.class);scanModel.setDisconfActiveBackupServiceClassSet(classdata);
​//// 获取DisconfUpdateService//classdata = reflections.getTypesAnnotatedWith(DisconfUpdateService.class);scanModel.setDisconfUpdateService(classdata);
​// update pipelineSet<Class<? extends IDisconfUpdatePipeline>> iDisconfUpdatePipeline = reflections.getSubTypesOf(IDisconfUpdatePipeline.class);if (iDisconfUpdatePipeline != null && iDisconfUpdatePipeline.size() != 0) {scanModel.setiDisconfUpdatePipeline((Class<IDisconfUpdatePipeline>) iDisconfUpdatePipeline.toArray()[0]);}
​return scanModel;}

获取到资源信息(比如DisconfFile 和DisconfFileItem )之后,读取DisConfFile类及其对应的DisconfFileItem信息,将它们放到disconfFileItemMap中,最后将这些信息存储到仓库DisconfCenterStore。这部分逻辑在ScanMgrImpl.firstScan方法中。

    /*** 扫描并存储(静态)** @throws Exception*/public void firstScan(List<String> packageNameList) throws Exception {
​LOGGER.debug("start to scan package: " + packageNameList.toString());
​// 获取扫描对象并分析整合scanModel = scanStaticStrategy.scan(packageNameList);
​// 增加非注解的配置scanModel.setJustHostFiles(DisconfCenterHostFilesStore.getInstance().getJustHostFiles());
​// 放进仓库for (StaticScannerMgr scannerMgr : staticScannerMgrList) {
​// 扫描进入仓库scannerMgr.scanData2Store(scanModel);
​// 忽略哪些KEYscannerMgr.exclude(DisClientConfig.getInstance().getIgnoreDisconfKeySet());}}

扫描入库之后,就该获取数据/注入/Watch了。

    public static DisconfCoreMgr getDisconfCoreMgr(Registry registry) throws Exception {
​FetcherMgr fetcherMgr = FetcherFactory.getFetcherMgr();
​//// 不开启disconf,则不要watch了//WatchMgr watchMgr = null;if (DisClientConfig.getInstance().ENABLE_DISCONF) {// Watch 模块watchMgr = WatchFactory.getWatchMgr(fetcherMgr);}
​return new DisconfCoreMgrImpl(watchMgr, fetcherMgr, registry);}

扫描按顺序做了以下几个事情:

1.初始化Disconf-client自己的配置模块。 2.初始化Scan模块。 3.初始化Core模块,并极联初始化Watch,Fetcher,Restful模块。 4.扫描用户类,整合分布式配置注解相关的静态类信息至配置仓库里。 5.执行Core模块,从disconf-web平台上下载配置数据:配置文件下载到本地,配置项直接下载。 6.配置文件和配置项的数据会注入到配置仓库里。 7.使用watch模块为所有配置关联ZK上的结点,如果节点不存在,客户端会自己创建节点

流程图为:

5.disconf第二次扫描

    /*** 第二次扫描, 动态扫描, 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();}
​// 注入数据至配置实体中// 获取数据/注入/Watchif (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 *******************************");}

5.配置动态更新机制

disconf的配置动态更新借助于zk的watch机制(watch机制是zk 3大重要内容之一,其余两个是zk协议和node存储模型)实现的,初始化流程会对配置文件注册watch,这样当配置文件更新时,会通知到discnof-client,然后disconf-client再从disconf-web中获取最新的配置并更新到本地,这样就完成了配置动态更新。

如果disconf-web更新配置文件时,zk-client收到事件通知时,会调用本地回调函数,业务逻辑会回调至此

/*** 当配置更新时,系统会自动 调用此回调函数<br/>* 这个函数是系统调用的,当有配置更新时,便会进行回调** @author liaoqiqi* @version 2014-5-16*/
public class DisconfSysUpdateCallback implements IDisconfSysUpdate {
​/****/@Overridepublic void reload(DisconfCoreProcessor disconfCoreMgr, DisConfigTypeEnum disConfigTypeEnum, String keyName)throws Exception {
​// 更新配置数据仓库 && 调用用户的回调函数列表disconfCoreMgr.updateOneConfAndCallback(keyName);}
}

DisconfFileCoreProcessorImpl.updateOneConfAndCallback()

    /*** 更新消息: 某个配置文件 + 回调*/@Overridepublic void updateOneConfAndCallback(String key) throws Exception {// 更新 配置updateOneConf(key);// 回调DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);callUpdatePipeline(key);}

更新配置时,首先更新仓库中值,然后更新bean属性值,配置更新回调是用户自定义的回调方法,也就是@DisconfUpdateService修饰的类。配置更新时流程是:

开发人员在前端更新配置 -> disconf-web保存数据并更新zookeeper -> zookeeper通知disconf-client -> disconf-client 从 disconf-web下载对应配置 -> 更新仓库和bean属性 -> 调用回调 -> 更新配置完成。

配置数据的获取

@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;}

6.注解介绍

@Component
@Scope("singleton")
@DisconfFile(filename = "instance-redis.properties")
public class InstanceJedisConfig {private String host;private int port;@DisconfFileItem(name = "redis.host", associateField = "host")public String getHost() {return host;}@DisconfFileItem(name = "redis.port", associateField = "port")public int getPort() {return port;}
}
  • @DisconfFile

    作用于类,表示这个类是instance-redis.properties文件的配置类。

  • @DisconfFileItem

    作用于成员变量,表示这个成员变量对应于配置文件中的哪个配置项。

    标记associateField是可选的,它表示此get方法相关连的域的名字,如果此标记未填,则系统会自动分析get方法,猜测其相对应于域名。强烈建议添加associateField标记,这样就可以避免Eclipse生成的Get/Set方法不符合 Java规范的问题。

  • @DisconfItem

    private String instantMessage;
    @DisconfItem(key="instant.message")
    public String getInstantMessage() {
    return instantMessage;
    }

    表示instantMessage成员变量对应于instant.message配置项。

  • @DisconfUpdateService

    定义一个Spring的组件类,实现IDisconfUpdate接口,加上@DisconfUpdateService注解,里面加上需要监听的配置类或者配置项的key,那么当配置文件或配置项发生变化时,就会回调该类的reload方法。

    比如es的连接,数据库的连接修改之后,我们需要重新初始化重连,就需要实现reload方

@Service
@Scope("singleton")
@DisconfFile(filename = "paopaoSliveLevel.properties")
@DisconfUpdateService(classes = {PaoPaoLevelConfig.class})
public class PaoPaoLevelConfig implements IDisconfUpdate {@Overridepublic void reload() throws Exception {init();}
}
  • @DisconfActiveBackupService

    标识需要进行主备切换的服务,需要指定它影响的配置数

Disconf原理和实践相关推荐

  1. Atitit.java jna  调用c  c++ dll的原理与实践  总结  v2  q27

    Atitit.java jna  调用c  c++ dll的原理与实践  总结  v2  q27 1. Jna简单介绍1 2. Jna范例halo owrld1 3. Jna概念2 3.1. (1)需 ...

  2. Atitit.软件兼容性原理与实践 v3 q326.docx

    Atitit.软件兼容性原理与实践 v3 q326.docx 1. 架构兼容性1 2. Api兼容性1 2.1. 新api  vs  修改旧的api1 3. Web方面的兼容性(js,html)1 3 ...

  3. 2018-2019-2 网络对抗技术 20165239Exp3 免杀原理与实践

    2018-2019-2 网络对抗技术 20165239 Exp3 免杀原理与实践 win10 ip地址 192.168.18.1 fenix ip地址为 192.168.18.128 (1)杀软是如何 ...

  4. 20155222卢梓杰 实验三 免杀原理与实践

    实验三 免杀原理与实践 1.正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion,自己利用shellcode编程等免杀工具或技巧 实验步骤如下 1.先对实验二中生 ...

  5. Webpack原理与实践

    简单了解了几个常用的插件,一般适合用于任何类型的项目,不管是否使用了框架.webpack为每个工作环节都预留了合适的钩子,扩展时只需要找到合适的时机去做合适的事情. 写在前面 webpack插件机制的 ...

  6. 图解Spark原理及实践----大数据技术栈12

    回顾:大数据平台技术栈 (ps:可点击查看),今天就来说说其中的Spark! 来自:ITPUB Spark 已经成为广告.报表以及推荐系统等大数据计算场景中首选系统,因效率高,易用以及通用性越来越得到 ...

  7. NLP汉语自然语言处理原理与实践

    NLP汉语自然语言处理原理与实践 作者:郑捷 ISBN号:9787121307652 出版时间:2017-01-01 出版社:电子工业出版社

  8. 《新一代SDN——VMware NSX 网络原理与实践》——导读

    ** 前言 ** 当企业需要搭建一个"云"的时候,无论它是公有云还是私有云,其基础架构一定涉及网络.计算和存储这三大块.NIST对云计算的定义中,明确提出了云中资源需要实现&quo ...

  9. Docker容器的原理与实践(上)

    本文来自网易云社区. 虚拟化 是一种资源管理技术,将计算机的各种资源予以抽象.转换后呈现出来, 打破实体结构间的不可切割的障碍,使用户可以比原本更好的方式来应用这些资源. Hypervisor 一种运 ...

最新文章

  1. java 分权分域什么意思_什么是分权分域管理?为什么要应用分权分域技术?
  2. 英特尔提出了一个数学公式,以此证明自动驾驶汽车的安全性
  3. centos 开发php扩展,【PHP扩展】centos给PHP安装扩展
  4. ble之Transmit window offset and Transmit window size
  5. 经验:在mysql中避免重复插入数据的4种方式
  6. 机器学习超级复习笔记
  7. 一个C++工程CPU占用100%问题的排查
  8. Egret入门学习日记 --- 第十二篇(书中 5.1节 内容)
  9. java讲师北京_Java工程师提升空间大,前途好,该如何跨入它的大门呢?
  10. 罪恶都市中文java版_搜神录之罪恶都市BT版
  11. 火星坐标系(GCJ-02坐标系),CGCS2000坐标系,WGS-84坐标系
  12. device or resource busy问题处理
  13. C#日期格式参考小结
  14. 鸿蒙杀戮手机电脑版,鸿蒙杀戮单职业
  15. 今天跟linux无关--我所喜爱Linux的游戏
  16. contiki 参考
  17. 英语听力采用计算机化考试,北京高考英语听力机考有什么特点?
  18. MYSQL—— TIME_TO_SEC函数与UNIX_TIMESTAMP函数区别
  19. Android系统的三种分屏显示模式
  20. manjaro安装gcc

热门文章

  1. Keepalived 高可用集群的使用
  2. Haskell学习笔记:List
  3. Presentation Context
  4. DM达梦数据库DIsql入门学习
  5. 打靶识别(Opencv For Unity惊现bug)
  6. 未来运营商数字化转型之架构规划
  7. 文件密使 v3.7 绿色
  8. 小微企业注册资金标准
  9. 解决Docker容器没有权限写入宿主机目录
  10. 关于计算机专业的英语演讲稿,关于计算机英语演讲稿.doc