SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)
作者 | 宇木木兮
来源 |今日头条
承接SpringCloud Alibaba系列——2Nacos核心源码分析(上),学习目标和第1章内容在上一篇文章中,本文只介绍Nacos配置中心的核心流程及原理
第2章 配置中心
2.1 第一次加载
2.1.1 启动环境装载
PropertySourceBootstrapConfiguration
1.spring启动run方法
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
2.进入prepareEnvironment3.在事件监听中添加
ApplicationEnvironmentPreparedEvent
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
最终调用ApplicationListener的onApplicationEvent方法
路径为:multicastEvent方法->invokeListener方法->doInvokeListener方法
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//调用onApplicationEvent方法
listener.onApplicationEvent(event);
} catch (ClassCastException var6) {
...
}
}
4.类
BootstrapApplicationListener 为ApplicationEnvironmentPreparedEvent事件后的处理5.bootstrapServiceContext方法中加载配置类
BootstrapImportSelectorConfiguration
builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
ConfigurableApplicationContext context = builder.run(new String[0]);
进入到BootstrapImportSelectorConfiguration中就有点眼熟了,这实际上就是我们之前springboot里面通过自动装配自动装载的一个配置类。BootstrapImportSelectorConfiguration类如下:
@Import({BootstrapImportSelector.class})
public class BootstrapImportSelectorConfiguration {
public BootstrapImportSelectorConfiguration() {
}
}
它通过Import注解导入了BootstrapImportSelector类,而BootstrapImportSelector类实际上就是实现了DeferredImportSelector接口。后面的逻辑就是去ClassPath下面找spring.factories文件,然后装配
BootstrapConfiguration.class接口的实现类
6.添加BootstrapImportSelector类7.找BootstrapConfiguration的配置,共2个jar包
NacosConfigBootstrapConfiguration类
PropertySourceBootstrapConfiguration类8.重新回到run方法
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
9.prepareContext会调用applyInitializers方法10.该方法会执行所有实现了接口
ApplicationContextInitializer的initialize方法,而PropertySourceBootstrapConfiguration刚好为其实现类
2.1.2 initialize
调用
PropertySourceBootstrapConfiguration的initialize方法
1.调用initialize方法2.调用locateCollection方法3.调用locator.locate(environment);走到实现类
NacosPropertySourceLocator加载循序为共享、扩展、自身,后续加载会替换之前加载,所以优先级为自身>扩展>共享
4.loadSharedConfiguration加载共享配置5.loadNacosConfiguration方法6.loadNacosDataIfPresent方法
7.nacosPropertySourceBuilder.build方法8.loadNacosData方法9.getConfigInner方法10.getServerConfig方法 2.2 动态感应
1.我们刚才讲了再spring.factories里面,还加载了一个类
NacosConfigBootstrapConfiguration
2.NacosConfigBootstrapConfiguration类会把NacosConfigManager作为一个Bean注入3.实例化NacosConfigManager时会实例化NacosConfigService 实例化NacosConfigService 4.实例化NacosConfigService的时候,会调用构造方法
public NacosConfigService(Properties properties) throws NacosException {
..
this.initNamespace(properties);
//用户登录信息
//在ServerHttpAgent方法中,实现了定时任务调度,登录Nacos(客户端获取配置、服务注册列表需要建立链接),时间是5秒一次
this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
//维护nacos服务列表
//实际调用的是ServerHTTPAgent中的start方法,该类通过定时任务维护nacos的列表
this.agent.start();
//更新维护配置
this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}
public synchronized void start() throws NacosException {
...
//维护serverUrllist
GetServerListTask getServersTask = new GetServerListTask(addressServerUrl);
for (int i = 0; i < initServerlistRetryTimes && serverUrls.isEmpty(); ++i) {
//判断服务列表是否发生改变,如果发生改变,则更新服务列表
getServersTask.run();
...
}
//将自己在丢到定时任务里面执行,执行时间是30秒一次
TimerService.scheduleWithFixedDelay(getServersTask, 0L, 30L, TimeUnit.SECONDS);
isStarted = true;
}
5.我们去看下ClientWorker
public ClientWorker(final HttpAgent agent, ConfigFilterChainManager configFilterChainManager, Properties properties) {
...
//进行长轮询
this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
return t;
}
});
this.executor.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
//多线程执行检查配置逻辑
ClientWorker.this.checkConfigInfo();
} catch (Throwable var2) {
ClientWorker.LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", var2);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
6.检查配置
public void checkConfigInfo() {
//cacheMap里面存放的是CacheData对象,
//当配置需要自动刷新时,会在cacheMap里面增加一条记录
//cacheMap的key由groupId和dataId组成,value是CacheData
int listenerSize = ((Map)this.cacheMap.get()).size();
//一个线程只处理3000,如果超过3000,新开线程,所以,不足3000只会在第一次执行LongPollingRunnable方法
int longingTaskCount = (int)Math.ceil((double)listenerSize / ParamUtil.getPerTaskConfigSize());
if ((double)longingTaskCount > this.currentLongingTaskCount) {
for(int i = (int)this.currentLongingTaskCount; i < longingTaskCount; ++i) {
this.executorService.execute(new ClientWorker.LongPollingRunnable(i));
}
this.currentLongingTaskCount = (double)longingTaskCount;
}
}
7.LongPollingRunnable方法
class LongPollingRunnable implements Runnable {
public void run() {
...
try {
//根据taskID拿到是不是我需要去监听的配置
Iterator var3 = ((Map)ClientWorker.this.cacheMap.get()).values().iterator();
while(var3.hasNext()) {
CacheData cacheData = (CacheData)var3.next();
if (cacheData.getTaskId() == this.taskId) {
cacheDatas.add(cacheData);
try {
//通过本地文件中缓存的数据和 cacheData集合中的数据进行比对,判断是否出现数据变化
ClientWorker.this.checkLocalConfig(cacheData);
//没有变化直接返回,如果有变化,需要通知监听器
if (cacheData.isUseLocalConfigInfo()) {
cacheData.checkListenerMd5();
}
} catch (Exception var13) {
ClientWorker.LOGGER.error("get local config info error", var13);
}
}
}
//通过长轮询机制,默认长轮询30s,如果30s内有配置更改返回,没有更改就30s后返回空
List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);
ClientWorker.LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
Iterator var16 = changedGroupKeys.iterator();
while(var16.hasNext()) {
...
try {
//通过检测到改动过的key去重新从配置中心拿取最新的值
String[] ct = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);
//更新本地缓存
CacheData cache = (CacheData)((Map)ClientWorker.this.cacheMap.get()).get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(ct[0]);
if (null != ct[1]) {
cache.setType(ct[1]);
}
ClientWorker.LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}", new Object[]{ClientWorker.this.agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(ct[0]), ct[1]});
} catch (NacosException var12) {
String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", ClientWorker.this.agent.getName(), dataId, group, tenant);
ClientWorker.LOGGER.error(message, var12);
}
}
var16 = cacheDatas.iterator();
...
} catch (Throwable var14) {
ClientWorker.LOGGER.error("longPolling error : ", var14);
ClientWorker.this.executorService.schedule(this, (long)ClientWorker.this.taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
}
checkLocalConfig检查本地配置三种情况:
- 如果isUseLocalConfifigInfo为false,但是本地缓存路径的文件是存在的,那么把isUseLocalConfifigInfo设置为true,并且更新cacheData的内容以及文件的更新时间
- 如果isUseLocalCOnfifigInfo为true,但是本地缓存文件不存在,则设置为false,不通知监听器
- isUseLocalConfifigInfo为true,并且本地缓存文件也存在,但是缓存的的时间和文件的更新时间不一致,则更新cacheData中的内容,并isUseLocalConfifigInfo设置为true
8.检查是否有配置改动
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws IOException {
StringBuilder sb = new StringBuilder();
Iterator var4 = cacheDatas.iterator();
...
boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();
//远程服务检测,如果有改动返回,如果没改动,就等待超时返回
return this.checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}