Nacos是什么,在nacos.io网址下解释的很清楚,这里大概做个简单的介绍。
- 服务(Service)是Nacos世界的一等公民。
- Nacos提供了对服务的实时健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。
- Nacos提供的服务
- 动态服务配置
- 服务注册与发现并管理
- 动态DNS服务
- 服务及其元数据管理
上边简单的介绍了一下Nacos,他的使用与配置在官网说的非常清楚,这里我们就不再赘述了。这里,就Nacos配置中心的实现,主攻一波源码实现,分析一下再说。
首先,我们先看一下Nacos官网给出的JAVA SDK 客户端部分的程序结构
我们就从NacosFactory开始,一步一步把Nacos作为配置中心的功能梳理一遍。
获取配置
首先看一下Nacos获取配置信息部分的代码:
Properties properties = new Properties();
//指定组名
String group = "DEFAULT_GROUP";
//指定dataId
String dataId = "webflux-test.yml";
//如果有命名空间,则需要指定命名空间
String namespace = "62f571c7-7593-4513-8de9-538426175db4";
properties.put("serverAddr", "81.70.10.96:8848");
properties.put("namespace", namespace);
//获取ConfigService
ConfigService configService = NacosFactory.createConfigService(properties);
String config = configService.getConfig(dataId, group, 5000);
System.out.println(config);
经过这个一行Java SDK的调用,我们就可以获取配置信息了。实现总体来说也很简单,我们来跟一下代码部分,客户端脉络就是通过NacosFactory来获取ConfigService。创建代码如下:
可以看到是使用反射的方式进行创建的。用反射的方式进行创建,我的理解是如果后续实现功能的类如果需要改变的话,可以直接修改配置文件就行了,而不用进入类中修改相应的实现代码。
下边主要看一下ConfigService的构造函数,也就是他的实现类,NacosConfigService的构造函数。
public NacosConfigService(Properties properties) throws NacosException {
//校验配置信息
ValidatorUtils.checkInitParam(properties);
String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
if (StringUtils.isBlank(encodeTmp)) {
this.encode = Constants.ENCODE;
} else {
this.encode = encodeTmp.trim();
}
initNamespace(properties);
//这部分使用的代理模式,其中执行了login操作,
//这个agent也作为load blance请求服务端使用
//这里先不说
this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
this.agent.start();
//获取配置主要是这一部分,ClientWorker是客户端主要工作的地方
this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}
这里先不深入CilentWorker代码,现在我们拿到了ConfigService的实例对象,开始获取配置的操作了。如下图所示,获取配置的操作有两个,一个是单纯获取配置,另一个是获取配置并监听,我们先看单纯获取配置的部分。
com.alibaba.nacos.client.config.NacosConfigService#getConfigInner这个是最终调用的方法,方法比较长,一共分为3部分。首先,从本地配置文件中获取,获取到了,就直接返回,如下:
其次,从远程获取,获取成功则保存快照信息到本地文件中。最后,如果远程获取失败,则从本地快照获取,防止远程连接丢失造成的服务不可用。这就是获取配置信息在客户端部分的主要源码、如下:
我们继续追踪远程获取配置信息的部分,其实是发送了一个Http的请求来进行配置信息的获取的。
我们找到对应的地址,然后来到服务端,看看配置文件是从哪儿获取来的。经过追踪,发现地址是:/v1/cs/configs,GET
进过一番寻找,在服务端的config module下,找到ConfigController,获取配置调用的方法为:com.alibaba.nacos.config.server.controller.ConfigController#getConfig。
ConfigController中引用了ConfigServletInner类,调用了com.alibaba.nacos.config.server.controller.ConfigServletInner#doGetConfig方法。如下图所示:
tryConfigReadLock方法根据key值来判断所要读取的配置当前的锁状态,如果可读,就进入lockResult>0的判断分支,进行内容读取。主要逻辑就是这一部分。
如图,如果配置文件中使用直接读取,则读取数据库数据或者使用raft+derby读取leader的数据。如果不是,则直接从文件中读取内容。最终,数据写入response中,配置内容请求成功。
关于使用raft协议来实现获取配置信息,以保证配置信息的一致性的内容,后续对JRaft进行专门的研究之后,再回过头来进行分析。
监听配置
我们开发系统的时候,有些配置项是需要可以实时进行变化的,Nacos作为配置中心,也是支持对配置改变的动态感知的,那么,Nacos这块儿是怎么实现的呢?
经过对源码的阅读,大概画了一下监听配置改变的实现方式:
在上边我们已经分析出了,获取配置的方法有两个。第一个直接获取配置,第二个就是获取配置然后对相应的配置进行监听,实际调用代码如下:
configService.getConfigAndSignListener(dataId, group, 5000, new com.alibaba.nacos.api.config.listener.Listener() {
@Override
public Executor getExecutor() {
return t -> {
System.out.println("nihao");
t.run();
};
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println(configInfo);
}
});
最后一个参数,是实现com.alibaba.nacos.api.config.listener.Listener接口的对象,有两个方法需要重写。这里就倒着来分析一波,看看在哪儿执行的这两个方法。我使用的是IDEA,按住Ctrl和鼠标左键就可以回溯到调用链的上一步,我们点击一下上述代码的getExecutor()方法,调用方法的地方是com.alibaba.nacos.client.config.impl.CacheData#safeNotifyListener方法,可以看见这里首先定义了一个Rannable的job,然后之下如下方法:先判断重写的getExecutor()方法返回值是否不为空,不为空则使用自定义的执行器执行job,为空则直接执行job.run()。
最后执行了Listener的receiveConfigInfo()方法并更新了本地缓存中的内容。
接着向上走,如下图所示,是checkListenerMd5方法,如果md5与缓存中的lastCallMd5不一致,就进行方法调用。
继续往上跟,来到com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable内部类。这是个实现了Rannale接口的实现,刚刚的方法调用在run方法的执行中进行。这里的run方法要做的工作是:首先根据taskId匹配得到需要处理的缓存任务,然后先判断是否使用本地配置,如果使用本地配置则不再进行远程请求,直接对比本地配置文件的md5是否相同。如果不使用本地配置并且有需要监听同步的缓存数据,则进行远程调用,进而获取到变动的key值,然手根据key得到本地缓存,进而查询配置信息并更新缓存。
方法最后,执行一个调用当前执行器的方法,进而达到循环拉取调用的目的。也就是说,这个方法是一直在运行的,那这个方法是从哪儿调用过来的呢,继续向前,是com.alibaba.nacos.client.config.impl.ClientWorker#checkConfigInfo方法。从方法中可以看出,会启用一个LongPollingRunnable,并给定一个i作为taskId,持续不断的执行任务。那cacheMap的数据是怎么添加的呢?稍后再看,继续向上跟。
最终发现,com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker的构造方法中调用了该方法。这就是上文在初始化ConfigService时初始化的CilentWorker。好了,到这里都已经明了了。
在生成ConfigService时new了CilentWorker,CilentWorker中启动了一个定时任务线程池,持续不断的进行监控,实现cacheData中监听配置信息的目的。那个cacheData到底是怎么来的呢?
回过头来看获取配置并添加Listener的方法,调用的方法如下:
在接着往里跟,看见了关键的地方,这里就是添加cache的地方,
在往里跟,终于看到了cache添加的地方。
经过这一路下来,客户端怎么请求并监听配置变化已经一目了然了。再次回到远程请求服务中配置信息的地方,我发现,每次远程请求会等待很长一段时间,响应才会返回,而如果修改了配置文件内容,就会立马返回配置有更新,并触发监听器的方法执行,那服务器端到底是怎么实现方法的呢?后续再说。
以上内容是由源码一点一点梳理的,如有错误,烦请交流指正。