Nacos不仅仅只是用于做注册中心,它还可以做配置中心使用。那么什么是配置中心呢?配置中心就是为分布式系统提供统一的外部配置服务端。
简单使用
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
添加bootstrap.yml
server:
port: 7777
spring:
application:
name: user-service
profiles:
active: dev
cloud:
config:
server-addr: localhost:8848
file-extension: yml
shared-configs:
- dataId: common-db.yml
group: "PROJECTDEMO"
refresh: true
extension-configs:
- dataId: ext-config.yml
group: "PROJECTDEMO"
refresh: true
启动服务即可使用配置中心了,它会将配置中心的配置引入项目

加载顺序
项目引入配置不仅仅只是引入已配的dataId,还会根据服务名,服务环境加载一些配置,比如我上面的服务是user-service环境是dev
那么他还会加载user-service-dev.yml、user-service.yml、user-service加上配置的共享配置common-db.yml,扩展配置ext-config.yml

那么这5种配置优先级是怎样的呢?从高到低
- user-service-dev.yml 同工程同环境
- user-service.yml 同工程不同环境
- user-service 同工程不同环境无扩展后缀
- ext-config.yml 扩展配置
- common-db.yml 共享配置
配置动态感知
配置现在可以统一管理,那么如果客户端可以动态感知到配置变化更新存储的配置Bean多好呢,Nacos也是支持这种功能的,
使用配置类来创建bean时,若要实现注入bean的刷新,需要在配置类和Bean创建方法上均加上@RefreshScope注解。在对应配置被修改后,所有开启了刷新的注入bean在下一次调用时会重新进行初始化并替换掉之前注入的Bean。
注意点,即使使用的是final修饰对象,配置更改也回导致对象更改,原因是@RefreshScope底层使用的是cglib动态创建子类来实现的
加载顺序源码分析
首先同样我们来到spring-cloud-starter-alibaba-nacos-config这个包下面的spring.factories,最终我们定位到NacosConfigBootstrapConfiguration
从NacosConfigBootstrapConfiguration分析得出加载nacos配置中心配置是NacosPropertySourceLocator(所有注入的bean都在这个类里面),在locate方法里面调用了配置的加载。至于为什么是这个方法那就要从springboot启动类开始
SpringApplication#run (spring-boot)
--prepareContext
----applyInitializers
------PropertySourceBootstrapConfiguration#initialize (spring-cloud)
--------locateCollection
----------locate (spring-cloud-nacos-config)
@Override
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
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();
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;
}
从这段代码基本可以分析共享配置优先级<扩展配置<内部规则配置
但内部规则的这些配置的加载顺序还没有发现
loadApplicationConfiguration
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
String fileExtension = properties.getFileExtension();
String nacosGroup = properties.getGroup();
// load directly once by default
// 加载{service-name}
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
// 加载{service-name}.{extension}
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// 加载{service-name}-{environment}.{extension}
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
从这里我们又可以发现内部规则的加载顺序
优先级:{service-name}-{environment}.{extension}->{service-name}.{extension}->{service-name}
综上优先级得出
- service-name}-{environment}.{extension}
- {service-name}.{extension}
- {service-name}
- 扩展配置
- 共享配置
至于怎么加载的就不详细展开了,最后是通过调用api去获取配置的
配置动态感知
首先同样我们来到spring-cloud-starter-alibaba-nacos-config这个包下面的spring.factories,最终我们定位到NacosConfigAutoConfiguration
同样我们来到NacosContextRefresher我们发现它实现了一个事件监听ApplicationListener<ApplicationReadyEvent>
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// many Spring context
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
private void registerNacosListenersForApplications() {
// 是否刷新配置,默认FALSE需要配置就是我们配置的refresh: true
if (isRefreshEnabled()) {
for (NacosPropertySource propertySource : NacosPropertySourceRepository
.getAll()) {
if (!propertySource.isRefreshable()) {
continue;
}
String dataId = propertySource.getDataId();
// 为每个dataId注册一个Nacos监听器
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
registerNacosListener
注册一个监听器,监听nacos配置中心是否变动并更新配置
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);
// 发布一个事件,事件监听器RefreshEventListener
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
}
});
// 监听器注册 发布配置后回调innerReceive方法
configService.addListener(dataKey, groupKey, listener);
}
RefreshEventListener
监听RefreshEvent事件,说明有配置发布了
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(RefreshEvent event) {
if (this.ready.get()) {
Set<String> keys = this.refresh.refresh();
}
}
this.refresh.refresh
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
// 将除了系统级别变量提取出来,为了后续更新
/* 系统级别变量: StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,*/
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
// 将配置放到环境变量里面去
addConfigFilesToEnvironment();
// 比对并更新
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
// 发布一个环境变更事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
public void refreshAll() {
// 将原来的配置缓存清除
super.destroy();
// 发布事件
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
配置文件Dump
服务端启动时从数据库中加载配置到本地磁盘上。服务端会根据心跳文件中保存的最后一次心跳时间,来判断全量dump还是增量dump配置数据(由心跳间隔6小时判断)。
全量 dump :先清空磁盘缓存,然后根据主键 ID 每次提取一千条配置刷进磁盘和内存
增量 dump :提取最近6小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去 比对一遍数据库,如果有改变的再同步一次,相比于全量dump减少一定的数据库IO和磁盘IO次数