Nacos配置自动刷新不生效
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nacos配置自动刷新不生效相关的知识,希望对你有一定的参考价值。
参考技术A 碰到两个问题1,修改nacos配置,服务加载配置时报错找不到$project.version,当前nacos-client版本是1.2.0,升级到1.4.0以上可解决。
2,修改配置没有动态刷新,@Value注解无效,改用配置类的方式@ConfigurationProperties,别忘了加上@RefreshScope。
Nacos基于Spring cloud客户端配置动态刷新,配置文件命名逻辑
我们知道Nacos可以作为配置中心来使用,并且Nacos官方给我们提供了基于spring cloud的自动导入模块,可以在项目中比较方便的引入,另外Nacos的spring cloud客户端配置可以动态刷新,接下来我们看下spring cloud nacos客户端怎么实现的,
一般我们在spring cloud项目中使用Nacos的配置中心功能时会引入如下pom:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
在githup的项目源码上,我们看到在META-INF/spring.factories
下的内容如下:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
org.springframework.context.ApplicationListener=\\
com.alibaba.cloud.nacos.logging.NacosLoggingListener
基于我们之前的分析springboot自动装配原理,底层源码分析,条件注解实现机制
这里会加载上述几个Bean,而Nacos 客户端配置实现和刷新主要在NacosConfigBootstrapConfiguration
和NacosConfigAutoConfiguration
,在NacosConfigAutoConfiguration
中注入了NacosContextRefresher
,这个主要是用来刷新的,NacosConfigBootstrapConfiguration
中注入了NacosConfigManager
和NacosPropertySourceLocator
,NacosConfigManager
是管理Nacos配置的工具类,而NacosPropertySourceLocator
借助NacosConfigManager
去Nacos服务器加载对应的配置信息。
NacosPropertySourceLocator
类继承结构如下:
可以看到,其实现了PropertySourceLocator
接口,通过之前的分析我们知道,spring boot在准备环境的时候会调用当前容器中所有PropertySourceLocator
的locate
方法,这里NacosPropertySourceLocator
的实现如下:
public PropertySource<?> locate(Environment env)
nacosConfigProperties.setEnvironment(env);
ConfigService configService = nacosConfigManager.getConfigService();
long timeout = nacosConfigProperties.getTimeout();
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;
这里有个细节需要注意下:如果我们没有配置 spring.cloud.nacos.config.prefix 的话,那么默认是获取 spring.application.name 作为该值,也就是所谓的dataIdPrefix
另外这里会加载如下三个配置:1. loadSharedConfiguration共享配置 2. loadExtConfiguration扩展配置 3. loadApplicationConfiguration加载应用配置
在Nacos中,每个应用除了本应用的配置外,还额外支持共享和扩展配置。对于共享配置和扩展配置,多个应用都可以获取,可以通过如下方式配置:
spring.cloud.nacos.config.extension-configs[0].data-id=ext001
spring.cloud.nacos.config.extension-configs[0].group=extGroup001
spring.cloud.nacos.config.extension-configs[0].refresh=true
spring.cloud.nacos.config.shared-configs[0].data-id==share001
spring.cloud.nacos.config.shared-configs[0].group=shareGroup
spring.cloud.nacos.config.shared-configs[0].refresh=true
需要注意的是,这里的data-id必须配置,否则报错。
对于loadSharedConfiguration
:
private void loadSharedConfiguration(
CompositePropertySource compositePropertySource)
List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
.getSharedConfigs();
if (!CollectionUtils.isEmpty(sharedConfigs))
checkConfiguration(sharedConfigs, "shared-configs");
loadNacosConfiguration(compositePropertySource, sharedConfigs);
private void loadNacosConfiguration(final CompositePropertySource composite,
List<NacosConfigProperties.Config> configs)
for (NacosConfigProperties.Config config : configs)
loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(),
NacosDataParserHandler.getInstance()
.getFileExtension(config.getDataId()),
config.isRefresh());
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);
这里简单分析下,是怎么去获取配置的,首先根据spring.cloud.nacos.config.shared-configs
的配置,逐个去加载的。对于文件的类型fileExtension,Nacos客户端通过判断dataId中是否有.,如果有.则获取最后一个.后面的内容为文件格式,如: cmmon-share.yml,返回的是yml,否则返回默认,为properties
如果不需要刷新,首先判断有没有加载过,如果加载过了,则直接从本地缓存加载,否则通过nacosPropertySourceBuilder.build(dataId, group, fileExtension,isRefreshable);
加载
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable)
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
private List<PropertySource<?>> loadNacosData(String dataId, String group,
String fileExtension)
String data = null;
try
data = configService.getConfig(dataId, group, timeout);
if (StringUtils.isEmpty(data))
return Collections.emptyList();
return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
fileExtension);
catch (Exception e)
return Collections.emptyList();
可以看到,最终通过Nacos的ConfigService
去进行实际的配置获取了。
对于loadSharedConfiguration
和loadExtConfiguration
两者基本一致,唯一不同的就是dataId不同,加载不同的配置。
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment)
// fileExtension 默认为 properties
String fileExtension = properties.getFileExtension();
// nacosGroup 默认为 DEFAULT_GROUP
String nacosGroup = properties.getGroup();
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
for (String profile : environment.getActiveProfiles())
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
这里首先回去加载以dataIdPrefix
为名称的配置,如果我们没配置spring.cloud.nacos.config.prefix
那么就会将spring.application.name
的配置,例如配置了spring.cloud.nacos.config.prefix=test001
,那么这里首先会获取dataId=test001
这个名称的配置。
然后在获取dataId=dataIdPrefix + DOT + fileExtension
的配置,这里可以理解为获取test001.properties
这个配置。
然后会获取当前spring上下文中active的profile,然后获取dataId= dataIdPrefix + SEP1 + profile + DOT + fileExtension
的配置,比如这个环境是测试,那么这里就会获取test001-test.propreties
这个配置。接着获取Nacos的配置方式与上面的都一样。
最后addFirstPropertySource
来加入到整个配置环境中去,这里loadSharedConfiguration
、loadExtConfiguration
、loadApplicationConfiguration
最后都会调用这个方法,即:
loadApplicationConfiguration 会覆盖loadExtConfiguration的配置,loadExtConfiguration会覆盖loadSharedConfiguration的配置。
这样,我们就把Nacos的配置都加载到当前Spring的环境中来了,那么怎么刷新呢 ?
刷新的机制在NacosContextRefresher
中实现,其实现了ApplicationListener<ApplicationReadyEvent>
接口:
public void onApplicationEvent(ApplicationReadyEvent event)
// many Spring context
if (this.ready.compareAndSet(false, true))
this.registerNacosListenersForApplications();
当spring容器全部就绪之后,就会触发ApplicationReadyEvent
事件,继而触发registerNacosListenersForApplications
事件:
private void registerNacosListenersForApplications()
if (isRefreshEnabled())
for (NacosPropertySource propertySource : NacosPropertySourceRepository
.getAll())
if (!propertySource.isRefreshable())
continue;
String dataId = propertySource.getDataId();
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
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);
这里通过configService.addListener(
向ConfigService
增加了监听器,Nacos客户端通过Long Polling的方式去Nacos服务器获取最新的配置,当有配置更新时,Nacos客户端则向Spring上下文发布一个RefreshEvent
事件,而根据之前spring cloud配置实现、刷新,spring cloud config实现原理,刷新
的分析,触发该事件之后,Spring boot会启动一个新的空的spring容器,这个容器会去加载一次整个配置,会告知应用哪些配置有变动。这时候被RefreshScope和ConfigurationProperties注解修饰的类会重新创建并初始化。
另外可以看到,这里在spring cloud中使用Nacos客户端去获取配置的时候通过
dataIdPrefix+'-"activeProfile+"."+fileExtension
,同时需要指定namespace和group
dataIdPrefix通过spring.cloud.nacos.config.prefix配置项获取,没有配置的话则获取spring.application.name
activeProfile则是我们启动spring cloud指定的profile
fileExtension通过spring.cloud.nacos.config.file-extension配置项获取,没有配置的话默认为properties
group通过spring.cloud.nacos.config.group配置项获取,没有配置的话默认为DEFAULT_GROUP
namespace通过spring.cloud.nacos.config.namespace获取,没有配置的话,默认public
以上是关于Nacos配置自动刷新不生效的主要内容,如果未能解决你的问题,请参考以下文章