上篇文章介绍了Nacos的服务发现(Naming)的客户端,这篇来聊下配置管理(Config)的客户端。

几个概念

首先引用官方文档提到的几个概念来帮助我们理解:

  • 配置集 (Configuration Set):一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。
  • 配置集 ID(Data ID):Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,
  • 配置分组(Group):Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。

此外,还有几个后文会用到的名词和变量需要说明一下:

  • tenant:租户信息,等同于namespace。
  • server_name:客户端给Nacos服务端的名称,根据配置情况可能有以下几种可能。前两种对应配置IP和端口的方式,后两种对应配置endpoint的方式。
namespace endpoint 对应的server_name
未设置 未设置 fixed-{IP1}_{port1}-{IP2}_{port2}
设置 未设置 fixed-{ip1}_{port1}-{ip2}_{port2}-{namespace}
未设置 设置 {endpoint}
设置 设置 {endpoint}-{namespace}

Example

同样,我们先来看官方示例。

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
String serverAddr = "localhost";
String dataId = "test";
String group = "DEFAULT_GROUP";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("receive:" + configInfo);
}

@Override
public Executor getExecutor() {
return null;
}
});

boolean isPublishOk = configService.publishConfig(dataId, group, "content");
System.out.println(isPublishOk);

Thread.sleep(3000);
content = configService.getConfig(dataId, group, 5000);
System.out.println(content);

boolean isRemoveOk = configService.removeConfig(dataId, group);
System.out.println(isRemoveOk);
Thread.sleep(3000);

content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
Thread.sleep(300000);

ConfigService

ConfigService的实现类是com.alibaba.nacos.client.config.NacosConfigService,对外提供了以下方法:

  • getConfig:获取对应{group}和{dataId}的配置。
  • addListener:注册监听,对应{group}和{dataId}的配置发生变化时回调。
  • publishConfig:发布对应{group}和{dataId}的配置。
  • removeConfig:删除对应{group}和{dataId}的配置。
  • removeListener:移除对应{group}和{dataId}的监听。
  • getServerStatus:获取Nacos服务端健康状态。

接下来分别从几个核心逻辑入手分析一下Config客户端的实现。

获取配置

调用getConfig方法会按照如图所示的逻辑返回配置。

获取配置的优先级依次为:本地配置 -> 服务端获取的配置 -> Snapshot保存的配置。
本地配置和Snapshot是由com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor类来管理的。

优先使用本地配置,如果我们需要在某台机器上使用跟线上环境不同的配置用于调试等场景,可以通过在本地添加配置来达到目的。
默认路径为{user.home}/nacos/config/{server_name}_nacos/data/config-data[-tenant/{tenant}【存在tenant时才有这一部分】]/{group}/{dataId}
示例:

  • /Users/darkness463/nacos/config/fixed-localhost_8848_nacos/config-data/DEFAULT_GROUP/test(未配置tenant)
  • /Users/darkness463/nacos/config/fixed-localhost_8848_nacos/config-data-tenant/test_tenant/DEFAULT_GROUP/test(tenant配置为test_tenant

服务端配置快照 (Configuration Snapshot)保存的是上次从Nacos服务端获取到的配置。
默认保存路径为{user.home}/nacos/config/{server_name}_nacos/data/snapshot[-tenant/{tenant}【存在tenant时才有这一部分】]/{group}/{dataId}
示例:

  • /Users/darkness463/nacos/config/fixed-localhost_8848_nacos/snapshot/DEFAULT_GROUP/test(未配置tenant)
  • /Users/darkness463/nacos/config/fixed-localhost_8848_nacos/snapshot-tenant/test_tenant/DEFAULT_GROUP/test(tenant配置为test_tenant

发布配置

发布配置逻辑比较简单,即通过请求Nacos服务端发布对应{tenant}、{dataId}、{group}的配置。
事实上,Nacos服务端还支持标签、归属应用等额外信息的配置,客户端也有对应的代码实现,但在目前版本的客户端中并未对外提供相应方法传入这些参数,可能在后续版本中会提供吧。

监听配置变化

初始化会启动两个线程池用于监听配置的变化。一个是线程数为1的线程池,线程名为com.alibaba.nacos.client.Worker.{server_name},每隔10毫秒会检查需不需要启动新任务来拉取配置。另一个线程池用于实际拉取配置,线程名为com.alibaba.nacos.client.Worker.longPolling.{server_name}

CacheData

com.alibaba.nacos.client.config.impl.CacheData这个类用于保存客户端设置的Listener信息,所有的CacheData都保存在AtomicReference<Map<String, CacheData>> cacheMap中,CacheData包含以下几个比较重要的属性:

1
2
3
4
5
6
7
8
9
10
11
private final String name;
public final String dataId;
public final String group;
public final String tenant;
private final CopyOnWriteArrayList<ManagerListenerWrap> listeners;

private volatile String md5;
private volatile boolean isUseLocalConfig = false;
private volatile long localConfigLastModified;
private volatile String content;
private int taskId;

我们可以对相同的{dataId}、{group}、{tenant}设置不同的Listener,这些Listener会被保存到同一个CacheData对象的listeners属性中。

taskId属性用于确定向服务端定期拉取配置时的任务批次,在新创建CacheData时确定,其生成逻辑为:

1
int taskId = cacheMap.get().size() / (int)ParamUtil.getPerTaskConfigSize();

其中ParamUtil.getPerTaskConfigSize()的默认值为3000,也就是说每3000个CacheData会启动一个任务,通过com.alibaba.nacos.client.Worker.longPolling.{server_name}线程来拉取配置。这个默认值可以通过在SystemProperties中设置key为PER_TASK_CONFIG_SIZE的值来改变。

LongPollingRunnable

LongPollingRunnable即用于从服务端拉取配置变化的任务,每个LongPollingRunnable都有一个taskId用于标记任务的编号,与上面CacheData的taskId具有同样的意义。
LongPollingRunnable按照以下逻辑执行:

其中本地配置即上文提到的获取配置时优先使用的本地配置,其变化情况分为:无->有,有->无,有变更这3种情况,根据变化情况会对CacheData中的isUseLocalConfig进行标记。对于使用本地配置的CacheData,将不会请求服务端。

同一批CacheData的信息拼接起来请求Nacos服务端,定义了2个分隔符:WORD_SEPARATORLINE_SEPARATORWORD_SEPARATOR用于分隔同一个CacheData的不同信息,值为(char)2LINE_SEPARATOR用于分隔不同的CacheData,值为(char)1
拼接的格式为:{dataId}{WORD_SEPARATOR}{group}{WORD_SEPARATOR}{MD5}[{WORD_SEPARATOR}{tenant}【存在tenant时才有这一部分】]{LINE_SEPARATOR}{dataId}{WORD_SEPARATOR}{group}{WORD_SEPARATOR}{MD5}[{WORD_SEPARATOR}{tenant}【存在tenant时才有这一部分】]。

请求参数示例:Listening-Configs=dataId^2group^2contentMD5^2tenant^1

在请求的header中添加了Long-Pulling-Timeout的header,默认值为30000,即30秒。Nacos服务端采用了异步Servlet,会比对请求的传过去的MD5,如果有更新,会返回更新了的配置,如果没有更新,会等待住30秒。

其他可配置项

除了前面已经提到的一些可配置项,还有一些可配置项可能在官方的文档里并没写,但从代码可以看到一些可调整的配置,但有部分配置实际并没有使用到,这里只列出来用到的。

本地配置和Snapshot

默认情况下,本地配置和Snapshot的路径前缀是{user.home},可以通过在SystemProperties中设置key为JM.SNAPSHOT.PATH的值修改。

tenant

可以通过以下几种方式设置tenant,按顺序获取。

  • 在初始化ConfigServiceProperties中设置,key为namespace
  • 在SystemProperties中设置,key为tenant.id
  • 在SystemProperties中设置,key为acm.namespace

网络相关参数

  • appKey:客户端身份信息,key为nacos.client.appKey
  • Nacos服务端端口:key为nacos.server.port,默认值8848。
  • 连接超时时间:key为NACOS.CONNECT.TIMEOUT,默认值1000。
  • 客户端版本:从application.properties中获取,key为version

总结

总体来说,配置管理(Config)的客户端逻辑比较简单,比较重要的只有监听配置变化这部分。

到此我们已经深入了解了Nacos的客户端,纵观整个客户端代码,服务发现(Naming)和配置管理(Config)似乎是强行整合到一起的,连Http相关的东西都用了两套,还出现了namespacetenant这样一个概念两种命名的情况。
建议要么把服务发现(Naming)和配置管理(Config)的客户端分开,要么整合得更完美一些。

后续我们来看看Nacos客户端是如何与Spring Cloud整合的。