SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)

老老老JR老北
发布于 2022-7-25 17:28
浏览
0收藏

作者 | 宇木木兮
来源 |今日头条

承接SpringCloud Alibaba系列——2Nacos核心源码分析(上),学习目标和第1章内容在上一篇文章中,本文只介绍Nacos配置中心的核心流程及原理

第2章 配置中心

SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区

2.1 第一次加载
2.1.1 启动环境装载
PropertySourceBootstrapConfiguration

1.spring启动run方法

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

2.进入prepareEnvironmentSpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区3.在事件监听中添加
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事件后的处理SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区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类SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区7.找BootstrapConfiguration的配置,共2个jar包

NacosConfigBootstrapConfiguration类SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区

PropertySourceBootstrapConfiguration类SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区8.重新回到run方法

this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

9.prepareContext会调用applyInitializers方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区10.该方法会执行所有实现了接口
ApplicationContextInitializer的initialize方法,而PropertySourceBootstrapConfiguration刚好为其实现类

2.1.2 initialize
调用
PropertySourceBootstrapConfiguration的initialize方法

1.调用initialize方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区2.调用locateCollection方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区3.调用locator.locate(environment);走到实现类
NacosPropertySourceLocatorSpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区加载循序为共享、扩展、自身,后续加载会替换之前加载,所以优先级为自身>扩展>共享

4.loadSharedConfiguration加载共享配置SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区5.loadNacosConfiguration方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区6.loadNacosDataIfPresent方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区

7.nacosPropertySourceBuilder.build方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区8.loadNacosData方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区9.getConfigInner方法SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区10.getServerConfig方法  SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区2.2 动态感应
1.我们刚才讲了再spring.factories里面,还加载了一个类
NacosConfigBootstrapConfigurationSpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区

2.NacosConfigBootstrapConfiguration类会把NacosConfigManager作为一个Bean注入SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区3.实例化NacosConfigManager时会实例化NacosConfigService SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区实例化NacosConfigService SpringCloud Alibaba系列——2Nacos配置中心源码分析(下)-鸿蒙开发者社区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);
}

分类
标签
已于2022-7-25 17:28:36修改
收藏
回复
举报
回复
    相关推荐