Nacos如何是怎么实现将多个配置项注入到同一个工程中的?

在使用Nacos的时候,有没有想过如何将多个配置同时注入到一个工程中?比如下面的一个场景:

多个项目工程的数据库配置一致,但是又有不同的私有化配置,根据抽象原则,我们是否可以将重复的配置信息单独放到一个共享的配置文件里,然后再给一些项目配置一个独享的配置文件呢?

在使用本地配置的时候,可以给工程指定多个yaml或者xml等文件,然后加载器会根据路径将全部文件依次加载到项目运行环境中,那么使用Nacos怎么办呢?

在Nacos中,提供了nacos.config.dataId配置参数,他可以接收一个配置文件名称,如果名称写错的话,会找不到配置文件。翻看一下com.alibaba.boot.nacos.config.properties.NacosConfigProperties类的话,可以发现在dataId下方有一个变量名叫dataIds

1
2
3
4
5
6
7
8
9
10
@ConfigurationProperties(NacosConfigConstants.PREFIX) // NacosConfigConstants.PREFIX = "nacos.config"
public class NacosConfigProperties {

private String serverAddr = "127.0.0.1:8848";

private String dataId;

private String dataIds;

}

OK!看到这里,我们知道了Nacos是可以配置多个配置文件的,但是如何配置,且使用什么字符作为分隔符呢?源代码里竟然没有说明,说好的javadoc呢?

既然没有注释,我们就只能看一下dataIds字段的是如何被解析的吧,代码在com.alibaba.boot.nacos.config.util.NacosConfigLoader#reqGlobalNacosConfig中(至于如何知道在这里的,你细品)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private List<NacosPropertySource> reqGlobalNacosConfig(Properties globalProperties, ConfigType type) {
List<String> dataIds = new ArrayList<>();
// 步骤1
if (StringUtils.isEmpty(nacosConfigProperties.getDataId())) {
// 步骤2
final String ids = environment.resolvePlaceholders(nacosConfigProperties.getDataIds());
// 步骤3
dataIds.addAll(Arrays.asList(ids.split(",")));
} else {
// 步骤4
dataIds.add(nacosConfigProperties.getDataId());
}
final String groupName = environment.resolvePlaceholders(nacosConfigProperties.getGroup());
final boolean isAutoRefresh = nacosConfigProperties.isAutoRefresh();
// 步骤5
return new ArrayList<>(Arrays.asList(reqNacosConfig(globalProperties, dataIds.toArray(new String[0]), groupName, type, isAutoRefresh)));
}

以上代码可以抽象为一个流程图:

image-20201028222225464

关于本文中最核心的代码应属ids.split(","),这句话告诉我们dataIds字段是以逗号,作为分隔符的,那么我们现在就可以潇洒的操作我们的配置了。

1
2
3
4
5
6
nacos.config.server-addr=127.0.0.1:8848
nacos.config.namespace=dev
nacos.config.data-ids=starter_error,starter
nacos.config.auto-refresh=true
nacos.config.type=yaml
nacos.config.bootstrap.enable=true

疑问:既然有了dataIds,为什么还需要dataId呢?

拓展

NacosConfigProperties中发现一个extConfig属性,这个是做什么用的呢?我们找到解析该属性的方法,就在reqGlobalNacosConfig方法下方com.alibaba.boot.nacos.config.util.NacosConfigLoader#reqSubNacosConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
private List<NacosPropertySource> reqSubNacosConfig(NacosConfigProperties.Config config, Properties globalProperties, ConfigType type) {
Properties subConfigProperties = buildSubNacosProperties(globalProperties, config);
ArrayList<String> dataIds = new ArrayList<>();
if (StringUtils.isEmpty(config.getDataId())) {
final String ids = environment.resolvePlaceholders(config.getDataIds());
dataIds.addAll(Arrays.asList(ids.split(",")));
} else {
dataIds.add(config.getDataId());
}
final String groupName = environment.resolvePlaceholders(config.getGroup());
final boolean isAutoRefresh = config.isAutoRefresh();
return new ArrayList<>(Arrays.asList(reqNacosConfig(subConfigProperties, dataIds.toArray(new String[0]), groupName, type, isAutoRefresh)));
}

从逻辑上发现和reqGlobalNacosConfig方法一样,那么解析出来的NacosPropertySource实例有什么用处呢?在方法调用处我们发现解析出来的实例和dataIds的配置信息同级。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void loadConfig() {
Properties globalProperties = buildGlobalNacosProperties();
MutablePropertySources mutablePropertySources = environment.getPropertySources();
// 步骤1
List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties, nacosConfigProperties.getType());
for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
// 步骤2
List<NacosPropertySource> elements = reqSubNacosConfig(config, globalProperties, config.getType());
// 步骤3
sources.addAll(elements);
}
...
}

步骤1和步骤2都是解析相关的配置信息,步骤3是将extConfig属性指定的配置文件加入到步骤1的list中。那么我们来看一下extConfig属性的定义com.alibaba.boot.nacos.config.properties.NacosConfigProperties.Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static class Config {

private String serverAddr;

private String endpoint;

private String namespace;

private String accessKey;

private String secretKey;

private String ramRoleName;

private String dataId;

private String dataIds;

private String group = Constants.DEFAULT_GROUP;

private ConfigType type;

private String maxRetry;

private String configLongPollTimeout;

private String configRetryTime;

private boolean autoRefresh = false;

private boolean enableRemoteSyncConfig = false;

private String username;

private String password;
}

该类的属性和外层NacosConfigProperties几乎一样,那么我们是否可以使用dataId和extConfig中的dataId来代替dataIds呢(有点脱裤子放x的感觉)?那么我们的配置就可以修改一下了。

1
2
3
4
5
6
7
8
9
10
11
12
nacos.config.server-addr=127.0.0.1:8848
nacos.config.namespace=dev
nacos.config.data-ids=starter
nacos.config.auto-refresh=true
nacos.config.type=yaml
nacos.config.bootstrap.enable=true

nacos.config.ext-config.server-addr=127.0.0.1:8848
nacos.config.ext-config.namespace=dev
nacos.config.ext-config.data-id=starter_error
nacos.config.ext-config.auto-refresh=true
nacos.config.ext-config.type=yaml

然后启动服务,看下控制台的日志输出:

image-20201028232556099 image-20201028232653686

从日志中看出是先加载了外层的配置,然后再加载extConfig的配置,那注入时间是否有区别呢?我们把数据库配置文件放在ext中,看看启动是否正常:

image-20201028234429362

哦吼~报错了,我们不能把核心配置放在extConfig中加载。

在上述配置中,我们发现extConfig中也有serverAddr等一系列配置属性,意思也就是我们可以同时将不同注册中心里的配置注入到一个项目工程里,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nacos.config.server-addr=127.0.0.1:8848
nacos.config.namespace=dev
nacos.config.data-ids=starter
nacos.config.auto-refresh=true
nacos.config.type=yaml
nacos.config.bootstrap.enable=true

nacos.config.ext-config[0].server-addr=127.0.0.2:8848
nacos.config.ext-config[0].namespace=dev
nacos.config.ext-config[0].data-id=starter_error
nacos.config.ext-config[0].auto-refresh=true
nacos.config.ext-config[0].type=yaml

nacos.config.ext-config[1].server-addr=127.0.0.3:8848
nacos.config.ext-config[1].namespace=dev
nacos.config.ext-config[1].data-id=starter_error_ext
nacos.config.ext-config[1].auto-refresh=true
nacos.config.ext-config[1].type=yaml

哦吼~功能好强大,同时我们也可以在extConfig中使用dataIds注入该配置中心的多个配置了。