配置中心 Nacos 原理 Nacos如何加载远程配置?

前言

​ 最近几天在看Nacos相关内容,它同时拥有注册中心配置中心的功能,比Spring Cloud Config和 Spring Cloud Eureka好用很多,在使用的过程中(目前仅使用配置中心相关功能),自己也产生过几个疑问(其实就是挖了下实现原理)并找到了答案,在此分享给大家。

Spring Cloud Config和 Spring Cloud Eureka问题:

  • Eureka的停止更新。
  • Config 配置修改刷新时需要SpringCloud Bus消息总线发出消息通知(Kafka、RabbitMQ等)到各个服务完成配置动态更新。
  • 没有相应的可视化控制台

nacos可以同时实现注册和配置中心,以及配置的动态更新。

Nacos不清楚的可以参考我的csdn文章,太基础的放在公众号不合适,浪费大家的时间

由于篇幅问题,本文先介绍第一个疑问Nacos如何加载远程配置(拉取远程配置的原理)并给到Spring环境使用?

Nacos如何加载远程配置?

远端配置如下

在这里插入图片描述

物理架构如下

在这里插入图片描述

启动Nacos ClientServer程序,用wireshark去抓包分析

wireshark抓包分析

设置过滤http协议目标ip为Nacos Server的地址
抓包结果如下,全部的请求都在这儿了。
在这里插入图片描述

追踪/nacos/v1/cs/configs?dataId=laker-dev.yaml...

结果如下:

GET /nacos/v1/cs/configs?dataId=laker-dev.yaml&group=DEFAULT_GROUP HTTP/1.1
Content-Type: application/json
Accept-Charset: UTF-8
Accept-Encoding: gzip
Content-Encoding: gzip
Client-AppName: unknown
Client-RequestTS: 1610697169595
Client-RequestToken: 0ae60c7db35f4bc668e25c76238a8c2b
Client-Version: 1.3.2
exConfigInfo: true
RequestId: c2bb905c-7454-4933-8a36-26f6068a8261
User-Agent: Java/1.8.0_102
Host: 10.7.11.13:8848
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

HTTP/1.1 200 
Config-Type: yaml
Content-MD5: 508ca88692b12ccd3321073447ec8ea3
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache,no-store
Last-Modified: Fri, 15 Jan 2021 06:41:27 GMT
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Jan 2021 07:52:49 GMT
Keep-Alive: timeout=60
Connection: keep-alive

laker:
  name: laker1222222

重点:远端返回的内容=我们本地在application.yml配置的内容一样

laker:
  name: laker1222222

原理分析

核心就是spring-cloud-context中的配置扩展接口PropertySourceLocator,Nacos 实现的类为NacosPropertySourceLocator

1. 当spring程序启动时会调用locate方法。

public PropertySource<?> locate(Environment env) {
   // 1. 创建一个跟远程打交道的对象NacosConfigService
   ConfigService configService = nacosConfigProperties.configServiceInstance();
   ... 省略代码
   // 2. 操作NacosPropertySource对象,下面三个方法最终都会调用该对象build
   nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
   // 3. 
   String name = nacosConfigProperties.getName();
   String dataIdPrefix = nacosConfigProperties.getPrefix();
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = name;
   }
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = env.getProperty("spring.application.name");
   }
   // 从远程获取的properties会存放到该类,最终放到Environment中
   CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
   // 加载公共模块配置
   loadSharedConfiguration(composite);
   // 加载扩展配置
   loadExtConfiguration(composite);
   // 加载独有配置
   loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
   return composite;
}

2. debug跟踪,真正获取远程配置的是 NacosConfigService调用getConfigInner()。

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
   ... 省略代码
    // 1. 优先使用failvoer配置
    String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
   ... 省略代码
    try {
        // 2. 服务器获取配置
        content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
   ... 省略代码
        return content;
    } catch (NacosException ioe) {
        if (NacosException.NO_RIGHT == ioe.getErrCode()) {
            throw ioe;
        }
    }
    // 3. 当服务器挂了就拿本地快照
    content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
   ... 省略代码
    return content;
}
--- 内容为
laker:
  name: laker1222222
  • 当服务器挂了就拿本地快照,这个机制挺好的,本地快照位置如下图:
    在这里插入图片描述
    内容如下
laker:
  name: laker1222222

3. 解析远端拿回的内容,NacosDataParserHandler. parseNacosData

在这里插入图片描述

支持格式如下:

  • Json
  • Properties
  • XML
  • Yaml

解析为MapPropertySource供Spring环境使用。

重复造轮子

准备

分析了一波,实现好像也很简单吗,我们自己来重复造轮子实现一个读取本地Json文件

Json内容如下

{
	"laker.name":"lakerttt"
}

Client的bootstrap.yml

laker:
  file: D://laker.json

1. 实现自定义PropertySourceLocator

public class LakerPropertySourceLocator implements PropertySourceLocator {

    @Override
    public PropertySource<?> locate(Environment environment) {
        String fileName = environment.getProperty("laker.file");
        
        FileReader reader = new FileReader(new File(fileName));
        String json = reader.readString();
        
		Map<String, Object> result = new LinkedHashMap<>(32);

		ObjectMapper mapper = new ObjectMapper();
		Map<String, Object> dataMap = mapper.readValue(json, LinkedHashMap.class);

        return new MapPropertySource("laker", dataMap);
    }
}

PropertySourceLocator 扩展接口,我们可以在该接口实现自定义配置的加载,比如从数据库中获取配置,或者文件中获取配置等。

配置可以是json、xml、yaml等等

2. 启用自定义PropertySourceLocator

@Configuration
public class LakerConfigConfiguration {
    @Bean
    public LakerPropertySourceLocator greizPropertySourceLocator() {
        return new LakerPropertySourceLocator();
    }
}

在resources的META-INF/spring.factories添加启动指定加载类

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.laker.config.LakerConfigConfiguration
  • BootstrapConfiguration简介

Spring.factories中的配置项, org.springframework.cloud.bootstrap.BootstrapConfiguration,会在Spring启动之前的准备上下文阶段将其加入到容器中。BootstrapConfiguration是在刷新(refresh)阶段将class作为预选的配置类@Configuration注入到容器中的,在@Configuration配置解析阶段会解析此class。

扩展

1. 配置文件 bootstrap和application区别

Spring Cloud 的官方文档解释如下:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_the_bootstrap_application_context

Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

因此,对比 application 配置文件,bootstrap 配置文件具有以下几个特性。

  • boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
  • boostrap 里面的属性不能被覆盖
bootstrap/ application 的应用场景

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap 配置文件有以下几个应用场景。

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性
  • 一些加密/解密的场景;

2. 加载位置与顺序

SpringBoot启动会扫描以下位置的application.properties/yml文件作为spring boot的默认配置文件:

file:./config/
file:./
classpath:/config/
classpath:/

以上顺序按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级的配置内容会覆盖低优先级配置的内容,其中配置文件中的内容是互补配置,即

  • 存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
  • 存在不同的内容的时候,高优先级和低优先级的配置内容取并集

3. 外部配置的加载顺序

Spring Boot也可以从以下位置加载配置: 优先级从高到低顺序,高优先级覆盖低优先级,如有不同内容,高优先级和低优先级形成互补配置

  • 命令行参数

命令行参数的优先级是最高的,假定内部配置的最高优先级配置文件配置的启动端口号是8081,启动命令行参数如以下设置:

java -jar spring-boot-02-config-02.0.0.1-SNAPSHOT.jar --server.port=8089

那么启动的端口就改成了8089,命令行可以把项目的所有的配置选项全部都改掉

  • 来自java:comp/env的JNDI属性

  • java系统属性(System.getProperties())

  • 操作系统环境变量

  • RandomValuePropertySource配置的random.\*属性值

  • jar包外部的application-{profile}.properties或者application.yml(带spring.profile)配置文件

  • jar包内部的application-{profile}.properties或者application.yml(带spring.profile)配置文件

  • jar包外部的application.properties或者application.yml(不带spring.profile)配置文件

  • jar包内部的application.properties或者application.yml(不带spring.profile)配置文件

  • @Configuration注解类上的@PropertySource

  • 通过SpringApplication.setDefaultProperties指定的默认属性

参考:

  • https://www.cnblogs.com/wolf-bin/p/11532708.html

本系列目录


🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lakernote

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值