一、NACOS配置如下

1.bootStrap.yaml 配置

spring:profiles:active: devapplication:name: newdaycloud:nacos:discovery:server-addr: www.nacos.com:8848#server-addr: 10.100.8.104:8848#server-addr: 172.18.173.44:8848config:file-extension: yamlrefresh-enabled: trueserver-addr: ${spring.cloud.nacos.discovery.server-addr}#namespace: akbnamespace: 8c30c80a-0644-415e-ab05-44696fd5378b
people:beanSize: 30
server:port: 8120

2.maven

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2.2.5.RELEASE</version></dependency>

二、加载原理

1.springboot在springApplication预处理环境对象会加载本地配置文件,然后创建spring cloud的application,接着会创建自身的web application,最后会预处理上下文信息。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);postProcessApplicationContext(context);applyInitializers(context);listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));listeners.contextLoaded(context);}

2.在springApplication.prepareContext中会调用所有实现了ApplicationContextInitializer的对象。

这里我们关心的是org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration这个配置初始化类,这是springcloud的外部配置加载类,他会调用容器中所有实现了PropertySourceLocator的对象,然后调用这些对象的locate方法生成一个属性资源对象,再统一添加到一个复合属性资源对象中,最后将这个复合属性资源对象加到环境对象中的属性资源列表中。

public void initialize(ConfigurableApplicationContext applicationContext) {CompositePropertySource composite = new CompositePropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME);AnnotationAwareOrderComparator.sort(this.propertySourceLocators);boolean empty = true;ConfigurableEnvironment environment = applicationContext.getEnvironment();for (PropertySourceLocator locator : this.propertySourceLocators) {PropertySource<?> source = null;source = locator.locate(environment);logger.info("Located property source: " + source);composite.addPropertySource(source);empty = false;}if (!empty) {MutablePropertySources propertySources = environment.getPropertySources();String logConfig = environment.resolvePlaceholders("${logging.config:}");LogFile logFile = LogFile.get(environment);if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);}insertPropertySources(propertySources, composite);reinitializeLoggingSystem(environment, logConfig, logFile);setLogLevels(applicationContext, environment);handleIncludedProfiles(environment);}}

3.com.alibaba.cloud.nacos.client.NacosPropertySourceLocator实现了PropertySourceLocator接口,所以被触发到。这个方法实际上就是先读取NACOS本地缓存文件,没有再去请求NACOS服务器读取网络配置文件,然后返回一个属性资源对象。

 public PropertySource<?> locate(Environment env) {nacosConfigProperties.setEnvironment(env);ConfigService configService = nacosConfigManager.getConfigService();nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,timeout);String name = nacosConfigProperties.getName();String dataIdPrefix = nacosConfigProperties.getPrefix();if (StringUtils.isEmpty(dataIdPrefix)) {dataIdPrefix = name;}if (StringUtils.isEmpty(dataIdPrefix)) {dataIdPrefix = env.getProperty("spring.application.name");}CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);loadSharedConfiguration(composite);loadExtConfiguration(composite);loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);return composite;}
loadSharedConfiguration(composite); //共享配置
loadExtConfiguration(composite);  //扩展配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); //应用配置。

4.我们重点看下读取网络应用配置的流程。最终会调用到

com.alibaba.nacos.client.config.impl.ClientWorker.getServerConfig
 public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)throws NacosException {String[] ct = new String[2];if (StringUtils.isBlank(group)) {group = Constants.DEFAULT_GROUP;}HttpRestResult<String> result = null;try {Map<String, String> params = new HashMap<String, String>(3);if (StringUtils.isBlank(tenant)) {params.put("dataId", dataId);params.put("group", group);} else {params.put("dataId", dataId);params.put("group", group);params.put("tenant", tenant);}result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);} catch (Exception ex) {String message = String.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s",agent.getName(), dataId, group, tenant);LOGGER.error(message, ex);throw new NacosException(NacosException.SERVER_ERROR, ex);}}}

读取到的数据为:

5.最终生成的属性资源对象截图如下

6.会加载三个配置,newday,newday.yaml,newday-dev.yaml

7.这里我们要注意,看下配置的优先级,nacos最高,然后是本地配置(application-dev,application,bootstrap)。

三、NACOS配置变化监听器初始化

1.com.alibaba.cloud.nacos.refresh.NacosContextRefresher实现了ApplicationListener<ApplicationReadyEvent>,这样在springApplication容器完全启动后,会触发其onApplicationEvent方法,注册NACOS 监听器。

public ConfigurableApplicationContext run(String... args) {ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();listeners.started(context);callRunners(context, applicationArguments);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;}

我们可以看到这是在springApplication所有的对象全部初始化好后,再发的通知事件。在listeners.running接口内。

2.NacosContextRefresher.onApplicationEvent只做一次注册NACOS监听器。

 public void onApplicationEvent(ApplicationReadyEvent event) {// many Spring contextif (this.ready.compareAndSet(false, true)) {this.registerNacosListenersForApplications();}}private void registerNacosListenersForApplications() {if (isRefreshEnabled()) {for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {if (!propertySource.isRefreshable()) {continue;}String dataId = propertySource.getDataId();registerNacosListener(propertySource.getGroup(), dataId);}}}

从下面我们可以看到是针对三个配置文件进行注册监听器。newday,newday.yaml,newday-dev.yaml 

3.NacosContextRefresher下面方法的内部listener为一个匿名函数,如下

private void registerNacosListener(final String groupKey, final String dataKey) {String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);Listener listener = listenerMap.computeIfAbsent(key,lst -> new AbstractSharedListener() {@Overridepublic void innerReceive(String dataId, String group,String configInfo) {refreshCountIncrement();nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);// todo feature: support single refresh for listeningapplicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));if (log.isDebugEnabled()) {log.debug(String.format("Refresh Nacos config group=%s,dataId=%s,configInfo=%s",group, dataId, configInfo));}}});try {configService.addListener(dataKey, groupKey, listener);}catch (NacosException e) {log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,groupKey), e);}}

4.然后会在clientWorker中的CacheMap属性中插入一个CacheData,这就是一个NACOS配置所对应的对象。里面包含dataid,group,md5,listener等。

 public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {String key = GroupKey.getKeyTenant(dataId, group, tenant);CacheData cacheData = cacheMap.get(key);if (cacheData != null) {return cacheData;}cacheData = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);// multiple listeners on the same dataid+group and race conditionCacheData lastCacheData = cacheMap.putIfAbsent(key, cacheData);if (lastCacheData == null) {int taskId = cacheMap.size() / (int) ParamUtil.getPerTaskConfigSize();cacheData.setTaskId(taskId);lastCacheData = cacheData;}// reset so that server not hang this checklastCacheData.setInitializing(true);LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.size());return lastCacheData;}

同时也会把刚才的匿名监听函数也设置到cacheData对象中。

    public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)throws NacosException {group = null2defaultGroup(group);String tenant = agent.getTenant();CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);for (Listener listener : listeners) {cache.addListener(listener);}}

四、NACOS配置接收数据变化通知流程

1.在clientWorker类中有两个线程池,一个检测配置,相当于调度器,一个长链接PULL检测服务器配置变化的工作线程,相当于执行器,定时读取cachemap的大小,当有新listener过来,判断是否要新增另一个工作线程(一个工作线程可以负责3000个配置监听),

    public void checkConfigInfo() {// Dispatch taskes.int listenerSize = cacheMap.size();// Round up the longingTaskCount.int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());if (longingTaskCount > currentLongingTaskCount) {for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {// The task list is no order.So it maybe has issues when changing.executorService.execute(new LongPollingRunnable(i));}currentLongingTaskCount = longingTaskCount;}}

2.具体的检测配置变化的工作线程,对每个配置文件循环请求NACOS服务器的listen接口,并设置最多阻塞30S长连接,服务器有变化就会在30S内实时返回变化的KEY信息。

长链接线程 public void run() {List<CacheData> cacheDatas = new ArrayList<CacheData>();List<String> inInitializingCacheList = new ArrayList<String>();try {// check failover configfor (CacheData cacheData : cacheMap.values()) {if (cacheData.getTaskId() == taskId) {cacheDatas.add(cacheData);try {checkLocalConfig(cacheData);if (cacheData.isUseLocalConfigInfo()) {cacheData.checkListenerMd5();}} catch (Exception e) {LOGGER.error("get local config info error", e);}}}// check server configList<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);if (!CollectionUtils.isEmpty(changedGroupKeys)) {LOGGER.info("get changedGroupKeys:" + changedGroupKeys);}for (String groupKey : changedGroupKeys) {String[] key = GroupKey.parseKey(groupKey);String dataId = key[0];String group = key[1];String tenant = null;if (key.length == 3) {tenant = key[2];}try {String[] ct = getServerConfig(dataId, group, tenant, 3000L);CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));cache.setContent(ct[0]);if (null != ct[1]) {cache.setType(ct[1]);}LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",agent.getName(), dataId, group, tenant, cache.getMd5(),ContentUtils.truncateContent(ct[0]), ct[1]);} catch (NacosException ioe) {String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",agent.getName(), dataId, group, tenant);LOGGER.error(message, ioe);}}for (CacheData cacheData : cacheDatas) {if (!cacheData.isInitializing() || inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {cacheData.checkListenerMd5();cacheData.setInitializing(false);}}inInitializingCacheList.clear();executorService.execute(this);} catch (Throwable e) {// If the rotation training task is abnormal, the next execution time of the task will be punishedLOGGER.error("longPolling error : ", e);executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);}}}

3.最终就会调用cachedata.checkListenerMd5,MD5变了,就是配置有变化,所以会通知listener.

    void checkListenerMd5() {for (ManagerListenerWrap wrap : listeners) {if (!md5.equals(wrap.lastCallMd5)) {safeNotifyListener(dataId, group, content, type, md5, wrap);}}}

4.listener最终会发送applicationEvent的刷新事件,强制容器重新加载数据。

springboot 读取nacos配置的流程和数据变化实时更新机制相关推荐

  1. SpringCloud读取Nacos配置中心报错:Could not resolve placeholder ‘xxx’ in value ‘${xxx}

    hello,我是灰小猿,一个超会写bug的程序员! 近期在写一个spring cloud Alibaba读取Nacos配置中心远程配置文件的内容时,出现了几个比较坑的bug,在此记录一下,帮大家避避坑 ...

  2. springboot中@Configuration配置类加载流程

    springboot中@Configuration配置类加载流程 代码位置 源码解读 每一步的分析 代码位置 ConfigurationClassParser#doProcessConfigurati ...

  3. IE缓存导致数据不能实时更新的解决办法

    查看全文 http://www.taodudu.cc/news/show-5902634.html 相关文章: IE缓存设置问题 清除IE缓存 关于IE缓存所带来的数据不能实时更新的解决办法 解决ie ...

  4. vue获取table一列数据_VUE table表格动态添加一列数据,新增的这些数据不可以编辑(v-model绑定的数据不能实时更新)...

    一.问题 用elementUi横着增加一行数据没毛病,可以操作 添加一列,这新增的这一列, 第一次去赋值的时候值是改了, 但没生效 点击下一行时 值就变过来 二.原因 横向添加 是复制上面的某一条数据 ...

  5. springboot整合nacos配置实现实时更新

    原文链接:http://wuwenliang.net/2019/02/22/springboot2-x%E6%95%B4%E5%90%88nacos%E9%85%8D%E7%BD%AE%E6%9C%8 ...

  6. Springboot整合Nacos配置中心

    前提 安装了Nacos服务端并登陆到其控制台页面. 创建一个Springboot工程. Nacos配置:(相关概念后面说明): 创建一个命名空间,当然,也有默认的命名空间. 创建配置文件: 整合 第一 ...

  7. 获取nacos配置中心文件值_Java 静态(static)方法读取 Nacos 配置中心

    为了降成本节省服务器资源,需要将配置中心从 Apollo 切换到 Nacos,因为注册中心是 Nacos 直接复用集群即可. 问题描述 util 里面获取配置的方法都是 static 静态方法,这就导 ...

  8. 关于IE缓存所带来的数据不能实时更新的解决办法

    IE缓存 为了提高访问网页的速度,IE会采用一种缓存机制,将你访问过的网页内容存在电脑里,然后当我们每次访问网站时,首先就会搜索有没有这些内容,如果有就直接从缓存中调出来,从而提高访问网站的速度,这本 ...

  9. springboot读取bootstrap配置及knife4j版本兼容性问题

    文章目录 项目框架说明 连环坑 问题一:springboot项目无法读取bootstrap.yml配置 问题二:启动报错,springboot与springcloud版本不匹配 问题三:启动报错Fai ...

最新文章

  1. 功能演示:戴尔PowerConnect 8024交换机VLAN的创建与删除
  2. ie 6 对注释标记的一个不稳定的错误
  3. java中的volatile和synchronized
  4. Python数据结构与算法(第二天)
  5. js解析二维码_最新最全阿里巴巴,今日头条,腾讯Flutter面试真题全解析(狂虐不止)...
  6. 08-百度ai语音合成
  7. vuecli启动的服务器位置,在vue cli 3生成的项目中启动dev服务器
  8. 第三次学JAVA再学不好就吃翔(part71)--BigDecimal类
  9. 微软发布 SQL Server 2019 新版本
  10. ExtJs UI框架学习六
  11. Android图片上传和下载,android 上传/下载 图片
  12. 教育|仝卓高考舞弊案细节曝光:为“恢复高考成绩”已起诉1年多
  13. xcode UIView常用方法属性动画
  14. 使用海康H5视频播放器开发包实现监控播放
  15. 如何做好数据分析的数据采集工作?
  16. 你真的会写for循环吗?来看看这些常见的for循环优化方式
  17. 箱形图适用于哪种数据_Excel 数据可视化:箱形图全面解析!
  18. Nginx反向代理与负载均衡应用实践(二)
  19. java 链表 置为null_Java: 链表head取出用后,置next=null为何可以加速gc?
  20. ipv4与ipv6的联系与区别

热门文章

  1. 销售订单按交货单合并Billing
  2. ABAP Submit 用法解析
  3. ABAP取字符串中的连续数字
  4. alv tree 总结
  5. 查找有权限使用某个T-Code的所有用户列表
  6. ALV GRID学习笔记----Double Click事件
  7. 2020年最畅销的20款电动汽车,特斯拉和五菱你偏向谁?
  8. 良品铺子,互联网经济下的“两元店”
  9. 手机耗电统计app_Android O新特性:精确统计APP电量消耗
  10. 绿联 蓝牙适配器 linux,绿联蓝牙适配器