前言

本以为这是个简单的问题,后来细看,这个问题不是抽完半包烟估计是看不出来的!源码调试环境和之前的有些不同,客户端实现配置中心需要添加额外依赖!如下!

<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

最好保持一致的版本,至于怎么配置情况往期文章​​Nacos作为服务配置中心​​这里版本请保持我上面配置的!那么还是原来的流程,上http接口!

Nacos-配置监听_Nacos


这个是获取配置详情的!

Nacos-配置监听_配置文件_02


这个是监听配置变化的,注意这里使用的是长轮询,并不是长链接,这里我发现阿里的中间件比较喜欢使用长轮询的方式,看过RocketMQ的朋友应该也知道RocketMQ使用的也是长轮询的机制获取消息的!由于Nacos-客户端配置中心比较复制,而且有多个监听,这里就不以之前分析服务注册和心跳机制那样一条线写下来了,而是分为多条线来写,这里多条线有些事并行,但是也是有启动顺序的,这让文章也稍微好写一点!

主线程初始化

简单介绍
这个线程只要就两个功能,第一个是开启一个10毫秒的定时任务,第二个就是将Nacos中对应的配置拉到本地目录,稍微简单点,不像其他监听线程,乱的一逼!

启动入口

Nacos-配置监听_长轮询_03


这个是SpringCloudContext牵引加载的

进入locate方法

Nacos-配置监听_Nacos_04


第一块代码进入后流程如下!加载Nacos配置中心

Nacos-配置监听_配置文件_05


进入NacosConfigService

Nacos-配置监听_客户端_06


NacosConfigService构造方法的代码如下:

  • 初始化一个HttpAgent,这里有调用装饰器模式,实际工作的类是ServerHttpAgent,MetricsHttpAgent内部也调用了ServerHttpAgent的方法,增加了监控统计信息
  • ClientWorker是一个客户端的一个工作类,agent作为参数传入ClientWorker,可以基本猜测到里面会用到agent做一些远程调用相关的事情!

这里ClientWorker创建是一个比较核心的方法!

切入ClientWorker

Nacos-配置监听_客户端_07


这里有三块比较重要的代码块,我们逐个分析!

  • 第一块是创建一个线程池,executor这个线程池是用来检查配置的
  • 第二块也是创建了一个线程池,但是目前并没有直接使用到,executorService这个线程主要是用于实现客户端定时长轮询的,也就是长轮询监听Nacos服务端配置变化的!
  • 第三块就是使用executor线程池开启一个定时任务,每隔10毫秒,执行checkConfigInfo();方法,检查一次配置信息

这里checkConfigInfo();方法比较有意思,也比较恶心!我们切入checkConfigInfo();方法!

深入checkConfigInfo();方法

Nacos-配置监听_配置文件_08

  • cacheMao:用来存储监听变更的缓存集合,key是根据dataId/group/tenant(租户)拼接的值,value是对应存储在Nacos服务器上的配置文件内容
  • 长轮询任务拆分:默认情况下,每个长轮询LongPollingRunnable任务处理3000个监听配置集,如果超过3000个,则需要启动多个LongPollingRunnable去执行

不幸的是这里服务刚开始启动,走到这里并不会进入这个if判断,也就是下面这部分代码根本不会执行

for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
// 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;

原因是currentLongingTaskCount的初始值是0,cacheMap的size也是0,所以这里的if进入逻辑不会执行,这里也就是每隔10毫秒执行到if后从新在执行,一致等待if判断条件准入!第一块代码就分析到这里,我们回到下面这个地方,开始分析第二块逻辑

Nacos-配置监听_Nacos_04


进入loadApplicationConfiguration方法

Nacos-配置监听_客户端_10


loadApplicationConfiguration这个方法中调用了loadNacosDataIfPresent,然后又循环调用loadNacosDataIfPresent,这里第一次调用dataId为server-consumer.yaml,第二次则是加了激活环境的,也就是server-consumer-dev.yaml

​​注意这里如果Nacos服务端没有添加对应配置文件,那么下面执行远程http请求Nacos服务的时候会404​​

Nacos-配置监听_Nacos_11

直接切入loadNacosDataIfPresent方法

Nacos-配置监听_配置文件_12


深入build方法

Nacos-配置监听_Nacos_13


调用loadNacosData方法

Nacos-配置监听_长轮询_14


直接进入getConfigInner方法

Nacos-配置监听_Nacos_15


这个方法其实逻辑比较简单,就是从本地配置,​​本地配置不是客户端程序内存​​ 等下进去各位就知道了,从本地配置中获取配置内容,如果不为空,那么第二块逻辑直接返回,如果为空那么

交给第三块逻辑发起远程请求从Nacos服务端获取对应配置

worker.getServerConfig(dataId, group, tenant, timeoutMs);

深入LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);方法

Nacos-配置监听_配置文件_16


这个LocalConfigInfoProcessor类建议先大致过一遍,就知道我刚才为什么标注红色的字体说不是客户端程序内存,而是本地配置文件!这里操作的就是本地配置文件!文件位于下面这个目录!

Nacos-配置监听_配置文件_17


注意,客户端程序刚启动的时候是没有这个数据文件的!那么按照代码逻辑,这里会向Nacos服务端发起http请求获取对应配置!那么我们进入getServerConfig方法深入getServerConfig方法

​​注意这里请求Nacos服务返回的结果是受dataId,group影响的,如果Nacos配置中心中不存在对应的配置,那么返回的状态码就不同!那么这里也就有了switch分支判断!如果对应查询配置不存在,那么就会返回状态码为404​​

Nacos-配置监听_长轮询_18

content = worker.getServerConfig(dataId, group, tenant, timeoutMs);

Nacos-配置监听_Nacos_19


Nacos-配置监听_长轮询_20


这里请求的接口和文章刚开始列出的Nacos文档中的API对上了!

Nacos-配置监听_客户端_21


然后根据http相应信息switch中更具http状态码判断分支逻辑

Nacos-配置监听_配置文件_22


这里都调用了LocalConfigInfoProcessor.saveSnapshot方法,无非就是最后有个config参数不同,这个参数也就是远程调用Nacos服务返回的配置信息了!切入saveSnapshot方法

Nacos-配置监听_配置文件_23


这里有个分支,会根据config是否为null,如果不为空,那么写入本地配置文件,如果为空的话,那么剔除掉文件!那么到这里主线程逻辑就分析完了,

至此,Nacos服务中的配置已经写到本地目录,然后客户端开启了一个10毫秒的线程检查cacheMap的size变化!

触发监听

简单介绍

监听ApplicationReadyEvent事件

Nacos-配置监听_长轮询_24

当主线任务执行好了后,会发布一个ApplicationReadyEvent事件,那么NacosContextRefresher中就监听了这个事件,那么就会进入NacosContextRefresher的onApplicationEvent方法

Nacos-配置监听_配置文件_25

进入registerNacosListenersForApplications方法

Nacos-配置监听_客户端_26


进入registerNacosListener方法

Nacos-配置监听_客户端_27


这里就是构造了Listener,和添加了Listener,进入addListener方法!

Nacos-配置监听_配置文件_28


调用addTenantListeners方法

Nacos-配置监听_Nacos_29

调用方法addCacheDataIfAbsent

Nacos-配置监听_客户端_30


这个方法有点巧妙!

CacheData cache = getCache(dataId, group, tenant);

getCache其实就是获取cacheMap中的数据,但是这里刚开始是没有值的,因为值还没加载到cacheMap中,还在本地配置文件中

cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);

这行代码创建CacheData对象就是读取本地配置文件,这个构造方法如下

Nacos-配置监听_配置文件_31


loadCacheContentFromDiskLocal方法就是去取本地配置的!最后回到最后那步代码

cacheMap.set(copy);

这行代码可谓是画龙点睛之作,这里往cacheMap中set了值,也就是说改变了cacheMap的size,那么主线程中开启的那个10毫秒的定时任务就会感知到!那么这部分逻辑就结束了

开启监听

触发监听部分也就是改变了cacheMap的大小,然后导致主线程中的定时任务感知到,那么我们把视角切回到主线程中的定时任务!

回看checkConfigInfo方法

Nacos-配置监听_客户端_32


这里使用executorService线程次开启了LongPollingRunnable线程任务!进入LongPollingRunnable线程任务对象

这块代码有点长,挺简单的,我们分块来看

Nacos-配置监听_配置文件_33


遍历cacheMap检查本地配置!

Nacos-配置监听_Nacos_34

List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);

通过长轮询请求Nacos服务器,检查服务端对应的配置是否发生变更,进入checkUpdateDataIds方法调用checkUpdateConfigStr方法

Nacos-配置监听_长轮询_35


和文章开头部分监听API对上了

Nacos-配置监听_长轮询_36


这是30秒的长轮询,如果30秒Nacos服务端查询的配置没有变动,那么返回空,如果30之内任意时间有变动立马返回,如果返回有变化,那么就会得到要获取的配置,通过

Nacos-配置监听_配置文件_37

String content = getServerConfig(dataId, group, tenant, 3000L);

getServerConfig和主线程部分的是同一个,逻辑也是一样,获取Nacos配置,然后写入到本地

Nacos-配置监听_Nacos_38


这一块又是重复执行当前任务!那么基本上到这里真个Nacos客户端获取配置源码就分析完了!