Spring Cloud Alibaba Nacos Config配置中心源码如何深度解析?

2026-05-21 03:503阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1668个文字,预计阅读时间需要7分钟。

Spring Cloud Alibaba Nacos Config配置中心源码如何深度解析?

前言+配置文件虽然家家户户都很熟悉,无论什么架构都离不开配置,当然Spring Boot已经大大简化了配置,服务环境也还不错,但管理配置起来还是相当麻烦,每次改完配置都需要重启服务。

前言

配置文件想必大家都很熟悉,无论什么架构都离不开配置,虽然spring boot已经大大简化了配置,但服务环境也好几个,管理配置起来还是很麻烦,并且每次改完配置都需要重启服务,nacos config出现就解决了这些问题,它把配置统一放到服务进行管理,客户端这边进行有需要的获取,可以实时对配置进行修改和发布

如何使用Nacos Config

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Nacos Config的使用方式:Nacos Config配置中心

spring boot启动容器如何加载nacos config配置文件

这个配置作用是spring在启动之间准备上下文时会启用这个配置来导入nacos相关配置文件,为后续容器启动做准备。

@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true) public class NacosConfigBootstrapConfiguration { public NacosConfigBootstrapConfiguration() { } @Bean @ConditionalOnMissingBean // 读取nacos相关配置 public NacosConfigProperties nacosConfigProperties() { return new NacosConfigProperties(); } @Bean @ConditionalOnMissingBean //实例化NacosConfigManager,创建ConfigService,ConfigService是配置读取更新的核心类 public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) { return new NacosConfigManager(nacosConfigProperties); } @Bean //spring初始化时会调用NacosPropertySourceLocator.locate方法, //nacos通过此方法加载nacos-server配置 public NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigManager nacosConfigManager) { return new NacosPropertySourceLocator(nacosConfigManager); } }
  • NacosConfigProperties:对应我们上面在bootstrap.properties中对应的配置信息

    Spring Cloud Alibaba Nacos Config配置中心源码如何深度解析?

  • NacosConfigManager:持有NacosConfigProperties和ConfigService,ConfigService用来查询发布配置的相关接口。

  • NacosPropertySourceLocator:它实现了PropertySourceLocator ,spring boot启动时调用PropertySourceLocator.locate(env)用来加载配置信息,下面来看相关源码:

/******************************************NacosPropertySourceLocator******************************************/ public PropertySource<?> locate(Environment env) { ConfigService configService = this.nacosConfigProperties.configServiceInstance(); if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); return null; } else { long timeout = (long)this.nacosConfigProperties.getTimeout(); this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = this.nacosConfigProperties.getName(); String dataIdPrefix = this.nacosConfigProperties.getPrefix(); if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = name; } if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); } CompositePropertySource composite = new CompositePropertySource("NACOS"); // 加载共享的配置文件 不同指定分组 默认DEFAULT_GROUP,对应配置spring.cloud.nacos.config.sharedDataids=shared_1.properties,shared_2.properties this.loadSharedConfiguration(composite); // 对应spring.cloud.nacos.config.ext-config[0].data-id=nacos.properties的配置 this.loadExtConfiguration(composite); // 加载当前应用配置 this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env); return composite; } } // 看一个加载实现即可 流程都差不多 具体实现在NacosPropertySourceBuilder.loadNacosData()方法完成 /******************************************具体实现在NacosPropertySourceBuilder******************************************/ private Properties loadNacosData(String dataId, String group, String fileExtension) { String data = null; try { // 向nacos server拉取配置文件 data = this.configService.getConfig(dataId, group, this.timeout); if (!StringUtils.isEmpty(data)) { log.info(String.format("Loading nacos data, dataId: '%s', group: '%s'", dataId, group)); // spring boot配置当然只支持properties和yaml文件格式 if (fileExtension.equalsIgnoreCase("properties")) { Properties properties = new Properties(); properties.load(new StringReader(data)); return properties; } if (fileExtension.equalsIgnoreCase("yaml") || fileExtension.equalsIgnoreCase("yml")) { YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean(); yamlFactory.setResources(new Resource[]{new ByteArrayResource(data.getBytes())}); return yamlFactory.getObject(); } } } catch (NacosException var6) { log.error("get data from Nacos error,dataId:{}, ", dataId, var6); } catch (Exception var7) { log.error("parse data from Nacos error,dataId:{},data:{},", new Object[]{dataId, data, var7}); } return EMPTY_PROPERTIES; }

至此我们在nacos上配置的properties和yaml文件都载入到spring配置文件中来了,后面可通过context.Environment.getProperty(propertyName)来获取相关配置信息

 

配置如何随spring boot加载进来我们说完了,接来下来看修改完配置后如何实时刷新

nacos config动态刷新

当nacos config更新后,根据配置中的refresh属性来判断是否刷新配置,配置如下

spring.cloud.nacos.config.ext-config[0].refresh=true

首先spring.factories 配置了EnableAutoConfiguration=NacosConfigAutoConfiguration,NacosConfigAutoConfiguration配置类会注入一个NacosContextRefresher,它首先监听了ApplicationReadyEvent,然后注册一个nacos listener用来监听nacos config配置修改后发布一个spring refreshEvent用来刷新配置和应用

public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware { private final NacosRefreshHistory nacosRefreshHistory; private ApplicationContext applicationContext; private final ConfigService configService; private Map<String, Listener> listenerMap = new ConcurrentHashMap<>(16); @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 只注册一次 if (this.ready.compareAndSet(false, true)) { this.registerNacosListenersForApplications(); } } private void registerNacosListenersForApplications() { if (isRefreshEnabled()) { for (NacosPropertySource propertySource : NacosPropertySourceRepository .getAll()) { // 对应刚才所说的配置 需要配置文件是否需要刷新 if (!propertySource.isRefreshable()) { continue; } String dataId = propertySource.getDataId(); // 注册nacos监听器 registerNacosListener(propertySource.getGroup(), dataId); } } } private void registerNacosListener(final String groupKey, final String dataKey) { String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey); Listener listener = listenerMap.computeIfAbsent(key, lst -> new AbstractSharedListener() { @Override public void innerReceive(String dataId, String group, String configInfo) { refreshCountIncrement(); // 添加刷新记录 nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo); // todo feature: support single refresh for listening // 发布一个spring refreshEvent事件 对应监听器为RefreshEventListener 该监听器会完成配置的更新应用 applicationContext.publishEvent( new RefreshEvent(this, null, "Refresh Nacos config")); if (log.isDebugEnabled()) { log.debug(String.format( "Refresh Nacos config group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo)); } } }); try { configService.addListener(dataKey, groupKey, listener); } catch (NacosException e) { log.warn(String.format( "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), e); } } }

我们说完了nacos config动态刷新,那么肯定有对应的动态监听,nacos config会监听nacos server上配置的更新状态

nacos config动态监听

一般来说客户端和服务端数据交互无非就两种方式

  • pull:客户端主动从服务器拉取数据
  • push: 由服务端主动向客户端推送数据

这两种模式优缺点各不一样,pull模式需要考虑的是什么时候向服务端拉取数据,可能会存在数据延迟问题,而push模式需要客户端和服务端维护一个长连接,如果客户端较多会给服务端造成压力,但它的实时性会更好。

 

nacos采用的是pull模式,但它作了优化,可以看做是pull+push,客户端会轮询向服务端发出一个长连接请求,这个长连接最多30s就会超时,服务端收到客户端的请求会先判断当前是否有配置更新,有则立即返回。

 

如果没有服务端会将这个请求拿住“hold”29.5s加入队列,最后0.5s再检测配置文件无论有没有更新都进行正常返回,但等待的29.5s期间有配置更新可以提前结束并返回,下面会在源码中讲解具体怎么处理的

Nacos Config动态刷新机制

nacos config 动态刷新流程图

nacos client处理

动态监听的发起是在ConfigService的实现类NacosConfigService的构造方法中,它是对外nacos config api接口,在之前加载配置文件和NacosContextRefresher构造方法中都会获取或创建

@Order(0) public class NacosPropertySourceLocator implements PropertySourceLocator { public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) { this.nacosConfigManager = nacosConfigManager; this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); } public PropertySource<?> locate(Environment env) { this.nacosConfigProperties.setEnvironment(env); ConfigService configService = this.nacosConfigManager.getConfigService(); //......省略其他 } } public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware { public NacosContextRefresher(NacosConfigManager nacosConfigManager, NacosRefreshHistory refreshHistory) { this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); this.nacosRefreshHistory = refreshHistory; this.configService = nacosConfigManager.getConfigService(); this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled(); } }

这里都会先判断是否已经创建了ConfigServer,没有则实例化一个NacosConfigService,来看它的构造函数

/***************************************** NacosConfigService *****************************************/ public class NacosConfigService implements ConfigService { /** * www.cnblogs.com/zzz-blogs/p/14249126.html

blog.csdn.net/qw852328952/article/details/112142451

blog.csdn.net/xingxinggua9620/article/details/113563116

本文共计1668个文字,预计阅读时间需要7分钟。

Spring Cloud Alibaba Nacos Config配置中心源码如何深度解析?

前言+配置文件虽然家家户户都很熟悉,无论什么架构都离不开配置,当然Spring Boot已经大大简化了配置,服务环境也还不错,但管理配置起来还是相当麻烦,每次改完配置都需要重启服务。

前言

配置文件想必大家都很熟悉,无论什么架构都离不开配置,虽然spring boot已经大大简化了配置,但服务环境也好几个,管理配置起来还是很麻烦,并且每次改完配置都需要重启服务,nacos config出现就解决了这些问题,它把配置统一放到服务进行管理,客户端这边进行有需要的获取,可以实时对配置进行修改和发布

如何使用Nacos Config

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Nacos Config的使用方式:Nacos Config配置中心

spring boot启动容器如何加载nacos config配置文件

这个配置作用是spring在启动之间准备上下文时会启用这个配置来导入nacos相关配置文件,为后续容器启动做准备。

@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true) public class NacosConfigBootstrapConfiguration { public NacosConfigBootstrapConfiguration() { } @Bean @ConditionalOnMissingBean // 读取nacos相关配置 public NacosConfigProperties nacosConfigProperties() { return new NacosConfigProperties(); } @Bean @ConditionalOnMissingBean //实例化NacosConfigManager,创建ConfigService,ConfigService是配置读取更新的核心类 public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) { return new NacosConfigManager(nacosConfigProperties); } @Bean //spring初始化时会调用NacosPropertySourceLocator.locate方法, //nacos通过此方法加载nacos-server配置 public NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigManager nacosConfigManager) { return new NacosPropertySourceLocator(nacosConfigManager); } }
  • NacosConfigProperties:对应我们上面在bootstrap.properties中对应的配置信息

    Spring Cloud Alibaba Nacos Config配置中心源码如何深度解析?

  • NacosConfigManager:持有NacosConfigProperties和ConfigService,ConfigService用来查询发布配置的相关接口。

  • NacosPropertySourceLocator:它实现了PropertySourceLocator ,spring boot启动时调用PropertySourceLocator.locate(env)用来加载配置信息,下面来看相关源码:

/******************************************NacosPropertySourceLocator******************************************/ public PropertySource<?> locate(Environment env) { ConfigService configService = this.nacosConfigProperties.configServiceInstance(); if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); return null; } else { long timeout = (long)this.nacosConfigProperties.getTimeout(); this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = this.nacosConfigProperties.getName(); String dataIdPrefix = this.nacosConfigProperties.getPrefix(); if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = name; } if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); } CompositePropertySource composite = new CompositePropertySource("NACOS"); // 加载共享的配置文件 不同指定分组 默认DEFAULT_GROUP,对应配置spring.cloud.nacos.config.sharedDataids=shared_1.properties,shared_2.properties this.loadSharedConfiguration(composite); // 对应spring.cloud.nacos.config.ext-config[0].data-id=nacos.properties的配置 this.loadExtConfiguration(composite); // 加载当前应用配置 this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env); return composite; } } // 看一个加载实现即可 流程都差不多 具体实现在NacosPropertySourceBuilder.loadNacosData()方法完成 /******************************************具体实现在NacosPropertySourceBuilder******************************************/ private Properties loadNacosData(String dataId, String group, String fileExtension) { String data = null; try { // 向nacos server拉取配置文件 data = this.configService.getConfig(dataId, group, this.timeout); if (!StringUtils.isEmpty(data)) { log.info(String.format("Loading nacos data, dataId: '%s', group: '%s'", dataId, group)); // spring boot配置当然只支持properties和yaml文件格式 if (fileExtension.equalsIgnoreCase("properties")) { Properties properties = new Properties(); properties.load(new StringReader(data)); return properties; } if (fileExtension.equalsIgnoreCase("yaml") || fileExtension.equalsIgnoreCase("yml")) { YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean(); yamlFactory.setResources(new Resource[]{new ByteArrayResource(data.getBytes())}); return yamlFactory.getObject(); } } } catch (NacosException var6) { log.error("get data from Nacos error,dataId:{}, ", dataId, var6); } catch (Exception var7) { log.error("parse data from Nacos error,dataId:{},data:{},", new Object[]{dataId, data, var7}); } return EMPTY_PROPERTIES; }

至此我们在nacos上配置的properties和yaml文件都载入到spring配置文件中来了,后面可通过context.Environment.getProperty(propertyName)来获取相关配置信息

 

配置如何随spring boot加载进来我们说完了,接来下来看修改完配置后如何实时刷新

nacos config动态刷新

当nacos config更新后,根据配置中的refresh属性来判断是否刷新配置,配置如下

spring.cloud.nacos.config.ext-config[0].refresh=true

首先spring.factories 配置了EnableAutoConfiguration=NacosConfigAutoConfiguration,NacosConfigAutoConfiguration配置类会注入一个NacosContextRefresher,它首先监听了ApplicationReadyEvent,然后注册一个nacos listener用来监听nacos config配置修改后发布一个spring refreshEvent用来刷新配置和应用

public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware { private final NacosRefreshHistory nacosRefreshHistory; private ApplicationContext applicationContext; private final ConfigService configService; private Map<String, Listener> listenerMap = new ConcurrentHashMap<>(16); @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 只注册一次 if (this.ready.compareAndSet(false, true)) { this.registerNacosListenersForApplications(); } } private void registerNacosListenersForApplications() { if (isRefreshEnabled()) { for (NacosPropertySource propertySource : NacosPropertySourceRepository .getAll()) { // 对应刚才所说的配置 需要配置文件是否需要刷新 if (!propertySource.isRefreshable()) { continue; } String dataId = propertySource.getDataId(); // 注册nacos监听器 registerNacosListener(propertySource.getGroup(), dataId); } } } private void registerNacosListener(final String groupKey, final String dataKey) { String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey); Listener listener = listenerMap.computeIfAbsent(key, lst -> new AbstractSharedListener() { @Override public void innerReceive(String dataId, String group, String configInfo) { refreshCountIncrement(); // 添加刷新记录 nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo); // todo feature: support single refresh for listening // 发布一个spring refreshEvent事件 对应监听器为RefreshEventListener 该监听器会完成配置的更新应用 applicationContext.publishEvent( new RefreshEvent(this, null, "Refresh Nacos config")); if (log.isDebugEnabled()) { log.debug(String.format( "Refresh Nacos config group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo)); } } }); try { configService.addListener(dataKey, groupKey, listener); } catch (NacosException e) { log.warn(String.format( "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), e); } } }

我们说完了nacos config动态刷新,那么肯定有对应的动态监听,nacos config会监听nacos server上配置的更新状态

nacos config动态监听

一般来说客户端和服务端数据交互无非就两种方式

  • pull:客户端主动从服务器拉取数据
  • push: 由服务端主动向客户端推送数据

这两种模式优缺点各不一样,pull模式需要考虑的是什么时候向服务端拉取数据,可能会存在数据延迟问题,而push模式需要客户端和服务端维护一个长连接,如果客户端较多会给服务端造成压力,但它的实时性会更好。

 

nacos采用的是pull模式,但它作了优化,可以看做是pull+push,客户端会轮询向服务端发出一个长连接请求,这个长连接最多30s就会超时,服务端收到客户端的请求会先判断当前是否有配置更新,有则立即返回。

 

如果没有服务端会将这个请求拿住“hold”29.5s加入队列,最后0.5s再检测配置文件无论有没有更新都进行正常返回,但等待的29.5s期间有配置更新可以提前结束并返回,下面会在源码中讲解具体怎么处理的

Nacos Config动态刷新机制

nacos config 动态刷新流程图

nacos client处理

动态监听的发起是在ConfigService的实现类NacosConfigService的构造方法中,它是对外nacos config api接口,在之前加载配置文件和NacosContextRefresher构造方法中都会获取或创建

@Order(0) public class NacosPropertySourceLocator implements PropertySourceLocator { public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) { this.nacosConfigManager = nacosConfigManager; this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); } public PropertySource<?> locate(Environment env) { this.nacosConfigProperties.setEnvironment(env); ConfigService configService = this.nacosConfigManager.getConfigService(); //......省略其他 } } public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware { public NacosContextRefresher(NacosConfigManager nacosConfigManager, NacosRefreshHistory refreshHistory) { this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); this.nacosRefreshHistory = refreshHistory; this.configService = nacosConfigManager.getConfigService(); this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled(); } }

这里都会先判断是否已经创建了ConfigServer,没有则实例化一个NacosConfigService,来看它的构造函数

/***************************************** NacosConfigService *****************************************/ public class NacosConfigService implements ConfigService { /** * www.cnblogs.com/zzz-blogs/p/14249126.html

blog.csdn.net/qw852328952/article/details/112142451

blog.csdn.net/xingxinggua9620/article/details/113563116