手写分布式配置中心(1)

简介: 1 什么是分布式配置中心其实就是把一些配置的信息分离于自身的系统,而这些信息又能被应用实时获取得到。这里用springboot 举例子,我们都知道springboot 启动的时候,会加载resource 目录下面的application.properties 或者 application.yml。 这个时候我们把springboot 启动的时候所需要加载的配置文件 不和工程放在一起,统一管理,这个就是分布式配置中心的核心思想。

1 什是分布式配置中心

其实就是把一些配置的信息分离于自身的系统,而这些信息又能被应用实时获取得到。这里用springboot 举例子,我们都知道springboot 启动的时候,会加载resource 目录下面的application.properties 或者 application.yml。 这个时候我们把springboot 启动的时候所需要加载的配置文件 不和工程放在一起,统一管理,这个就是分布式配置中心的核心思想。

1.1 分布式配置中心有哪些组成

1.1.1, 有一个界面能操作配置

1.1.2, 数据能够持久化(防止丢失,服务下线在启动配置还是存在的)

1.1.3, 存在客户端和服务端, 客户端主动去拉去数据或者服务端主动推送数据。 并且刷新本机的配置。(核心)

1.1.4, 一些管理界面的操作日志, 权限系统等。

2,市面上主流的配置中心

2.1 阿里的 nacos

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施. 具体使用请看官网

2.2 nacos 的原理,就是service 监控配置是否发生改变,通过长链接在发送给客户端

架构原理图如下 ,这个是推送模式, 但是是基于长链接
1.png

2.3 携程的Apollo

Apollo是携程框架部研发并开源的一款生产级的配置中心产品,它能够集中管理应用在不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。具体使用请看官网

携程的架构原理图也是和 Nacos 的是一样的, 也是长链接轮询, 服务端推送的模式。

2.3 spirgcloud config

springCloudConfig,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git/svn仓库中。

2.png

服务启动的时候config Service 会从远程git拉取配置文件,并存入到本地git文件库,当远程git不可用时,会从本地git文件库拉取配置信息. 具体的使用请以官网为准。

2.4 百度disconf

Disconf是百度开源出来的一款基于Zookeeper的分布式配置管理软件。目前很多公司都在使用,包括滴滴、百度、网易、顺丰等公司。通过简单的界面操作就可以动态修改配置属性,还是很方便的。使用Disconf后发现的一大好处是省却应用很多配置,而且配置可以自动load,实时生效。

基本原理图:当然具体的肯定要比这个更加的复杂, 这个只是主要的流程。

3.png

3如何实现自己的分布式配置中心

3.1 动态修改本地@Value注解的配置

3.2 在不同的bean 中, 相同的value 怎么同时修改。

4 具体思路

怎么动态修改@Value注解 的配置, 我们就要知道springboot 怎么加载application.ym 或者application.properties 文件的。

我这里用springboot2.1.1 的代码做示范。

首先打开

4.png

找到spring.factors 的配置文件

5.png

我们看到上面三个就是配置文件的加载器, 这里也显示了为啥properties 的优先级比yaml 的优先级高。 他是从上往下的顺序排列的啊。 但是真正的配置文件执行的还是下面的

我们点击去看一下实现类

ConfigFileApplicationListene

@Override

public void postProcessEnvironment(ConfigurableEnvironment environment,

      SpringApplication application) {

      //怎么加载资源

   addPropertySources(environment, application.getResourceLoader());

}

protected void addPropertySources(ConfigurableEnvironment environment,

      ResourceLoader resourceLoader) {

   RandomValuePropertySource.addToEnvironment(environment);

   // 把资源给load 到环境变量里面

   new Loader(environment, resourceLoader).load();

}

// 再用 propertySource 解析器给解析

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {

   this.environment = environment;

   this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(

         this.environment);

   this.resourceLoader = (resourceLoader != null) ? resourceLoade

         : new DefaultResourceLoader();

   this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(

         PropertySourceLoader.class, getClass().getClassLoader());

}

那我们也就知道了,也就是我们要是能 EnvironmentPostProcessor ,在重写里面的方法也就可以动态的加载配置文件了。 下面我们就开始代码实现.

4.1 代码实现

那既然是这样我们就可以实现EnvironmentPostProcessor 这个类通过接口动态的加载配置文件了。

那下面就是具体的代码实现

@Autowired
ConfigurableEnvironment configurableEnvironment;


@Autowired
Environment environment;



@Test
public void test() {

    String name = environment.getProperty("name");

    System.out.printf("动态加载之前" +name);

    Map<String,String> map = new HashMap<>();

    map.put("name","嘟嘟");

    configurableEnvironment.getPropertySources().addLast(

            new OriginTrackedMapPropertySource("xxxx.xml", map)

    );

    String property = environment.getProperty("name");

    System.out.printf("动态加载之后" +property);

}

4.1.2 单元测试

最终的结果是

6.png

我们现在解决了第一个问题, 怎么动态增加环境变量

第二个问题在@value 注解上使用的怎么动态刷新啊。

那这个使用我们就需要ConfigurablePropertyResolver 这个类,来解析这个key , 在 找到@value 对应的bean 通过反射来刷新

具体代码

4.2 代码实现

public static void refreshBean(Object bean, ConfigurablePropertyResolver propertyResolver) {

    // 定义EL表达式解释器

    SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    TemplateParserContext templateParserContext= new TemplateParserContext();



    String keyResolver, valueResolver = null;

    Object parserValue;

    // 获取真实对象属性



    Field[] declaredFields = bean.getClass().getDeclaredFields();

    boolean cglib = Arrays.stream(declaredFields).anyMatch(x -> x.getName().contains("CGLIB"));

    // 如果是cglib 代理找其父类

    if(cglib){

        declaredFields = bean.getClass().getSuperclass().getDeclaredFields();

    }



    // 遍历Bean实例所有属性

    for (Field field : declaredFields) {

        // 判断field是否含有@Value注解

        if (field.isAnnotationPresent(Value.class)) {

            // 读取Value注解占位符

            keyResolver = field.getAnnotation(Value.class).value();

            try {

                // 读取属性值

                valueResolver = propertyResolver.resolveRequiredPlaceholders(keyResolver);

                // EL表达式解析

                // 兼容形如:@Value("#{'${codest.five.url}'.split(',')}")含有EL表达式的情况

                Expression expression = spelExpressionParser.parseExpression(valueResolver, templateParserContext);

                if(field.getType() == Boolean.class){

                    parserValue =Boolean.valueOf(expression.getValue().toString());

                }

                else if(field.getType() == Integer.class){

                    parserValue =Integer.valueOf(expression.getValue().toString());

                }

                else if(field.getType() == Long.class){

                    parserValue =Long.valueOf(expression.getValue().toString());

                }else {

                    parserValue = expression.getValue(field.getType());



                }



            } catch (IllegalArgumentException e) {

                continue;

            }

            // 判断配置项是否存在

            if (Objects.nonNull(valueResolver)) {

                field.setAccessible(true);

                try {

                    field.set(bean, parserValue);

                    continue;

                } catch (IllegalAccessException e) {

                    e.printStackTrace();

                }

            }

        }

    }

我们在写一个单元测试测试

4.2.1 单元测试

7.png

8.png

@Autowired

ConfigurableEnvironment configurableEnvironment;

@Autowired

ConfigurablePropertyResolver configurablePropertyResolver;


@Autowired
Person person;


@Test
public void test() {

    System.out.printf("动态加载之前" +person.getName());

    Map<String,Object> map = new HashMap<>();

    map.put("name","嘟嘟");

    configurableEnvironment.getPropertySources().forEach( x->{

        if (x instanceof OriginTrackedMapPropertySource ) {

            Map<String,Object>  map1 = (Map<String, Object>) x.getSource();

            map1.putAll(map);

        }

    }

    );

    refreshBean(person,configurablePropertyResolver);

    System.out.printf("动态加载之后" +person.getName());

}

最后结果:

9.png

完美, 下一期我们在解决 多个bean, @value 的值一样怎么同时刷新。

相关文章
|
20天前
|
UED 存储 数据管理
深度解析 Uno Platform 离线状态处理技巧:从网络检测到本地存储同步,全方位提升跨平台应用在无网环境下的用户体验与数据管理策略
【8月更文挑战第31天】处理离线状态下的用户体验是现代应用开发的关键。本文通过在线笔记应用案例,介绍如何使用 Uno Platform 优雅地应对离线状态。首先,利用 `NetworkInformation` 类检测网络状态;其次,使用 SQLite 实现离线存储;然后,在网络恢复时同步数据;最后,通过 UI 反馈提升用户体验。
34 0
|
1月前
|
Java 测试技术 Spring
分布式之配置中心
分布式之配置中心
34 1
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
人工智能平台PAI产品使用合集之如何配置cluster系统自动生成分布式参数
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。
|
3月前
|
网络安全 数据安全/隐私保护
分布式系统详解--框架(Hadoop-Ssh免密登陆配置)
分布式系统详解--框架(Hadoop-Ssh免密登陆配置)
36 0
|
4月前
|
Cloud Native Java 开发工具
云原生 阿里云分布式文件系统 对象存储OSS 服务配置
【1月更文挑战第8天】云原生 阿里云分布式文件系统 对象存储OSS 服务配置
|
4月前
|
安全
考虑极端天气线路脆弱性的配电网分布式电源和储能优化配置模型
考虑极端天气线路脆弱性的配电网分布式电源和储能优化配置模型
|
4月前
|
调度
互动环境下分布式电源与电动汽车充电站的优化配置方法研究-全文复现matlab
互动环境下分布式电源与电动汽车充电站的优化配置方法研究-全文复现matlab
单向/双向V2G环境下分布式电源与电动汽车充电站联合配置方法(matlab代码)
单向/双向V2G环境下分布式电源与电动汽车充电站联合配置方法(matlab代码)
|
4月前
|
调度
考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法(matlab代码)
考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法(matlab代码)
|
4月前
|
存储 分布式计算 Hadoop
[绝对要收藏]配置hadoop完全分布式环境
[绝对要收藏]配置hadoop完全分布式环境
40 0