九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置

这篇具有很好参考价值的文章主要介绍了九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

熟悉Spring的小伙伴都知道,Spring 提供了强大的扩展机制。其中包括 **ApplicationContextInitializer**,该扩展是在上下文准备阶段(prepareContext),容器刷新之前做一些初始化工作,比如我们常用的配置中心 client 基本都是继承该初始化器,在容器刷新前将配置从远程拉到本地,然后封装成 PropertySource 放到 Environment 中供使用。

在 SpringCloud 场景下,SpringCloud 规范中提供了 PropertySourceBootstrapConfiguration实现了 ApplicationContextInitializer接口,另外还提供了个 PropertySourceLocator,二者配合完成配置中心的接入。

九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置,# Nacos源码,Nacos源码,Nacos配置中心,Nacos配置原理,Nacos拉取配置流程

从上述截图可以看出,在 PropertySourceBootstrapConfiguration 这个单例对象初始化的时候会将 Spring 容器中所有的 PropertySourceLocator 实现注入进来。然后在 initialize()方法中循环所有的 PropertySourceLocator 进行配置的获取,从这儿可以看出 SpringCloud 应用是支持我们引入多个配置中心实现的,获取到配置后调用 insertPropertySources 方法将所有的 PropertySource(封装的一个个配置文件)添加到 Spring 的环境变量 environment 中。

如果我们要使用Nacos的配置中心功能,需要在pom.xml中引入对应的依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>

跟nacos自动注册和服务发现一样,nacos配置中文也是利用了springboot的自动装配原理来实现的。

我们查看spring-cloud-starter-alibaba-nacos-config-2.2.9.RELEASE.jar的META-INF/spring.factories文件:

九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置,# Nacos源码,Nacos源码,Nacos配置中心,Nacos配置原理,Nacos拉取配置流程

上图展示了在 spring-cloud-starter-alibaba-nacos-config 包提供的自动装配类中引入了NacosConfigBootstrapConfiguration类。我们查看其定义:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigProperties nacosConfigProperties() {
		return new NacosConfigProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigManager nacosConfigManager(
			NacosConfigProperties nacosConfigProperties) {
		return new NacosConfigManager(nacosConfigProperties);
	}

	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigManager nacosConfigManager) {
		return new NacosPropertySourceLocator(nacosConfigManager);
	}

	/**
	 * Compatible with bootstrap way to start.
	 * @param beans configurationPropertiesBeans
	 * @return configurationPropertiesRebinder
	 */
	@Bean
	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
	@ConditionalOnNonDefaultBehavior
	public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
			ConfigurationPropertiesBeans beans) {
		// If using default behavior, not use SmartConfigurationPropertiesRebinder.
		// Minimize te possibility of making mistakes.
		return new SmartConfigurationPropertiesRebinder(beans);
	}

}

从源码可以看到,NacosConfigBootstrapConfiguration自动配置类注入了NacosPropertySourceLocator这个bean,该类继承自上述说的 PropertySourceLocator,重写了 locate 方法进行配置的读取。

九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置,# Nacos源码,Nacos源码,Nacos配置中心,Nacos配置原理,Nacos拉取配置流程我们来分析下 NacosPropertySourceLocator#locate()的代码:

public PropertySource<?> locate(Environment env) {
    nacosConfigProperties.setEnvironment(env);
    // 通过反射创建出一个NacosConfigService实例
    ConfigService configService = nacosConfigManager.getConfigService();

    if (null == configService) {
        log.warn("no instance of config service found, can't load config from nacos");
        return null;
    }
    long timeout = nacosConfigProperties.getTimeout();
	
	// 配置获取(使用 configService)、配置封装、配置缓存等操作
    nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
            timeout);
    String name = nacosConfigProperties.getName();

    String dataIdPrefix = nacosConfigProperties.getPrefix();
    if (StringUtils.isEmpty(dataIdPrefix)) {
        dataIdPrefix = name;
    }

    if (StringUtils.isEmpty(dataIdPrefix)) {
        dataIdPrefix = env.getProperty("spring.application.name");
    }

    CompositePropertySource composite = new CompositePropertySource(
            NACOS_PROPERTY_SOURCE_NAME);

	// 加载共享的配置信息
    loadSharedConfiguration(composite);
	// 加载扩展的配置信息
    loadExtConfiguration(composite);
	// 加载应用自身的配置信息
    loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    return composite;
}

可以看到 Nacos 启动会加载以下三种配置文件,加载到配置文件后会封装成 NacosPropertySource 返回。

  • loadSharedConfiguration():加载共享配置
private void loadSharedConfiguration(
        CompositePropertySource compositePropertySource) {
	// 获取共享配置
    List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
            .getSharedConfigs();
    if (!CollectionUtils.isEmpty(sharedConfigs)) {
        checkConfiguration(sharedConfigs, "shared-configs");
		// 加载配置
        loadNacosConfiguration(compositePropertySource, sharedConfigs);
    }
}
  • loadExtConfiguration():加载扩展配置
private void loadExtConfiguration(CompositePropertySource compositePropertySource) {
	// 获取扩展配置
    List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties
            .getExtensionConfigs();
    if (!CollectionUtils.isEmpty(extConfigs)) {
        checkConfiguration(extConfigs, "extension-configs");
        // 加载配置
        loadNacosConfiguration(compositePropertySource, extConfigs);
    }
}
  • loadApplicationConfiguration():加载应用自身配置
private void loadApplicationConfiguration(
        CompositePropertySource compositePropertySource, String dataIdPrefix,
        NacosConfigProperties properties, Environment environment) {
    // 配置文件扩展名
    String fileExtension = properties.getFileExtension();
    // 配置组名
    String nacosGroup = properties.getGroup();
    // 不带扩展名后缀查询,application
    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
            fileExtension, true);
    // 带扩展名后缀查询,application.yml
    loadNacosDataIfPresent(compositePropertySource,
            dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
    // 带环境,带扩展名后缀查询,application-prod.yml
    for (String profile : environment.getActiveProfiles()) {
        String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
        loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                fileExtension, true);
    }

}

加载应用自身的配置信息loadApplicationConfiguration()时,同时会加载以下三种配置,分别是:

  • 1、不带扩展名后缀查询,application
  • 2、带扩展名后缀查询,application.yml
  • 3、带环境,带扩展名后缀查询,application-prod.yml

依据后面加载的属性会覆盖掉之前加载的,可知,从上到下,优先级依次增高。

加载配置的核心方法是loadNacosDataIfPresent():

private void loadNacosDataIfPresent(final CompositePropertySource composite,
        final String dataId, final String group, String fileExtension,
        boolean isRefreshable) {
    if (null == dataId || dataId.trim().length() < 1) {
        return;
    }
    if (null == group || group.trim().length() < 1) {
        return;
    }
    NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
            fileExtension, isRefreshable);
    this.addFirstPropertySource(composite, propertySource, false);
}

private NacosPropertySource loadNacosPropertySource(final String dataId,
        final String group, String fileExtension, boolean isRefreshable) {
    if (NacosContextRefresher.getRefreshCount() != 0) {
        if (!isRefreshable) {
            return NacosPropertySourceRepository.getNacosPropertySource(dataId,
                    group);
        }
    }
    return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
            isRefreshable);
}

nacosPropertySourceBuilder.build()方法调用 loadNacosData 获取配置,然后封装成 NacosPropertySource,并且将该对象缓存到 NacosPropertySourceRepository中,后续会用到。

NacosPropertySource build(String dataId, String group, String fileExtension,
        boolean isRefreshable) {
    // 获取配置
    List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
            fileExtension);
	// 将配置封装成NacosPropertySource
    NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
            group, dataId, new Date(), isRefreshable);
	// 缓存NacosPropertySource:ConcurrentHashMap<String, NacosPropertySource> NACOS_PROPERTY_SOURCE_REPOSITORY
    // key:{dataId,group}
    NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
    return nacosPropertySource;
}

我们来看看如何获取配置的loadNacosData():

private List<PropertySource<?>> loadNacosData(String dataId, String group,
        String fileExtension) {
    String data = null;
    try {
        // 通过NacosConfigService加载配置内容
        data = configService.getConfig(dataId, group, timeout);
        if (StringUtils.isEmpty(data)) {
            log.warn(
                    "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
                    dataId, group);
            return Collections.emptyList();
        }
        if (log.isDebugEnabled()) {
            log.debug(String.format(
                    "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
                    group, data));
        }
        // 解析返回的字符串
        return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
                fileExtension);
    }
    catch (NacosException e) {
        log.error("get data from Nacos error,dataId:{} ", dataId, e);
    }
    catch (Exception e) {
        log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
    }
    return Collections.emptyList();
}

loadNacosData 方法中会将实际配置加载请求委托给NacosConfigService去做,然后解析返回的字符串,解析器实现了 PropertySourceLoader 接口,支持 yml、properties、xml、json 这几种。

public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
}

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
    // 默认组名:DEFAULT_GROUP
    group = blank2defaultGroup(group);
    // 校验组名、dataId
    ParamUtils.checkKeyParam(dataId, group);
    ConfigResponse cr = new ConfigResponse();
    // 设置配置信息,通过 namespace, dataId, group能唯一定位到一个配置文件
    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);

    // We first try to use local failover content if exists.
    // A config content for failover is not created by client program automatically,
    // but is maintained by user.
    // This is designed for certain scenario like client emergency reboot,
    // changing config needed in the same time, while nacos server is down.

    // 首先尝试使用本地如果存在的故障转移的配置内容,如果能读到失败转移的配置信息,则直接返回了。
    // 优先使用失败转移,设计的目的是当server挂后,又需要修改配置,就可以读本地目录
    String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
        cr.setContent(content);
        String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
        cr.setEncryptedDataKey(encryptedDataKey);
        configFilterChainManager.doFilter(null, cr);
        content = cr.getContent();
        return content;
    }

    try {
        // 去服务端拉取,这个就是正常逻辑,获取服务端存储的配置信息
        ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
        cr.setContent(response.getContent());
        cr.setEncryptedDataKey(response.getEncryptedDataKey());
        configFilterChainManager.doFilter(null, cr);
        content = cr.getContent();

        return content;
    } catch (NacosException ioe) {
        if (NacosException.NO_RIGHT == ioe.getErrCode()) {
            throw ioe;
        }
        LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", worker.getAgentName(), dataId, group, tenant, ioe.toString());
    }

    // 非鉴权失败的异常(NacosException.NO_RIGHT)的,尝试从本地快照中获取配置
    content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
    }
    cr.setContent(content);
    String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant);
    cr.setEncryptedDataKey(encryptedDataKey);
    configFilterChainManager.doFilter(null, cr);
    content = cr.getContent();
    return content;
}

从源码可以看到,首先获取本地缓存文件的配置内容(故障转移的配置内容),如果有直接返回;如果从本地没找到相应配置文件,则通过grpc请求服务端拉取;如果grpc请求报除了鉴权失败之外的异常(NacosException.NO_RIGHT),例如超时,还会尝试从本地快照中获取配置,不至于偶尔超时了就配置没了。

至此,在项目启动的时候(上下文准备阶段)通过NacosPropertySourceLocator就拉取到了远程 Nacos 中的配置信息,并且封装成 NacosPropertySource对象,然后PropertySourceBootstrapConfiguration依靠ApplicationContextInitializer机制(容器刷新之前支持一些自定义初始化工作),将前面封装好的NacosPropertySource对象放到了 Spring 的环境变量Environment 中。文章来源地址https://www.toymoban.com/news/detail-826981.html

到了这里,关于九、Nacos源码系列:Nacos配置中心原理(一)- SpringCloud应用启动时拉取配置的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringCloud-搭建Nacos配置中心

    Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一个分布式服务注册、配置管理,以及服务健康管理平台。在微服务架构中,配置管理是至关重要的一环,Nacos 提供了可靠、动态的配置管理功能,为分布式系统的配置中心提供了便捷的解决方案。 Nacos的主要功能图:

    2024年02月20日
    浏览(39)
  • SpringCloud实用篇1——eureka注册中心 Ribbon负载均衡原理 nacos注册中心

    单体架构: 将业务的所有功能集中在一个项目中开发,打成一个包部署。 优点:架构简单;部署成本低(打jar包、部署、负载均衡就完成了) 缺点:耦合度高(维护困难、升级困难,不利于大项目开发) 分布式架构 根据业务功能对系统做拆分,每个业务功能模块作为独立

    2024年02月13日
    浏览(27)
  • SpringCloud之Nacos配置中心解读

    目录 基本介绍  概述 动态配置服务 多配置格式编辑器  微服务拉取配置流程 Data ID @RefreshScope 配置共享的优先级 实战使用 简单的共享配置 开发环境的共享配置 环境隔离-命名空间Namespace 业务隔离-Group分组 概述 Nacos除了可以做注册中心,同样可以做配置管理来使用。 所谓

    2024年02月03日
    浏览(45)
  • 注册中心/配置管理 —— SpringCloud Alibaba Nacos

    Nacos 是一个易于使用的动态服务发现、配置和服务管理平台,用于构建云原生的应用程序 Nacos 的关键特性包括以下几项: 服务发现和服务健康监测:服务提供者使用原生 SDK、OpenAPI 等注册服务后,服务消费者可以使用 HTTPAPI 查找和发现服务。Nacos 提供对服务的实时健康检查

    2024年02月14日
    浏览(42)
  • SpringCloud 配置中心【Nacos_Config】

    目录 1. SpringCloud Config 2. Nacos_Config 3. 使用 Nacos Config 添加/配置 外部配置文件 3.1 添加/配置命名空间 3.2 添加/配置 外部配置文件 3.3 外部文件克隆到其他空间 4. 新建子模块【config_client】 4.1 父模块pom文件依赖结构          4.2 子模块pom文件结构 4.3 配置子模块 bootstrap.yml文件

    2024年02月01日
    浏览(44)
  • SpringCloud + Gateway(网关) + Nacos(注册中心+配置中心)+ Dubbo(内部服务调用)

    Apache Dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力 1、协议支持方面 Feign更加优雅简单。Feign是通过REST API实现的远程调用,基于Http传输协议,服务提供者需要对外暴露Http接口供消费者调用,服务粒度是http接口级的。通过短连接的方式进行通信,

    2024年02月06日
    浏览(51)
  • 【微服务SpringCloud-Alibaba】:Nacos 配置中心

    在 SpringCloud 中,我们使用了 Config 组件管理所有配置文件,使用了 Bus 消息总线更新配置,两者需要配合使用才能动态的管理配置文件。而 Nacos 可以替代这两个组件,实现动态的管理所有配置文件。 2.1、添加配置文件 想要管理配置,先要有配置文件。在使用 Config 组件管理配

    2023年04月27日
    浏览(41)
  • SpringCloud使用nacos做配置中心,不生效,没有作用

            一直使用nacos做注册中心,配置还是使用的配置文件。将配置文件整理到nacos以后,却发现一直不生效,看网上大部分说是版本依赖不兼容的问题,修改版本依赖关系、尝试降版本后发现都未生效。         新建bootstrap.yaml配置文件,将 Nacos本身的相关配置必须都

    2024年02月06日
    浏览(52)
  • SpringCloud Alibaba集成 Gateway(自定义负载均衡器)、Nacos(配置中心、注册中心)、Loadbalancer

    路由(route):路由是网关最基础的部分,路由信息由一个ID,一个目的URL、一组断言工厂和一 组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。 断言(Predicate):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Sprin

    2024年04月12日
    浏览(45)
  • Java之SpringCloud Alibaba【三】【微服务Nacos-config配置中心】

    Java之SpringCloud Alibaba【一】【Nacos一篇文章精通系列】 跳转 Java之SpringCloud Alibaba【二】【微服务调用组件Feign】 跳转 Java之SpringCloud Alibaba【三】【微服务Nacos-config配置中心】 跳转 Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】 跳转 Java之SpringCloud Alibaba【五】【微服务

    2024年02月16日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包