springboot 读取nacos配置的流程和数据变化实时更新机制
一、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配置的流程和数据变化实时更新机制相关推荐
- SpringCloud读取Nacos配置中心报错:Could not resolve placeholder ‘xxx’ in value ‘${xxx}
hello,我是灰小猿,一个超会写bug的程序员! 近期在写一个spring cloud Alibaba读取Nacos配置中心远程配置文件的内容时,出现了几个比较坑的bug,在此记录一下,帮大家避避坑 ...
- springboot中@Configuration配置类加载流程
springboot中@Configuration配置类加载流程 代码位置 源码解读 每一步的分析 代码位置 ConfigurationClassParser#doProcessConfigurati ...
- IE缓存导致数据不能实时更新的解决办法
查看全文 http://www.taodudu.cc/news/show-5902634.html 相关文章: IE缓存设置问题 清除IE缓存 关于IE缓存所带来的数据不能实时更新的解决办法 解决ie ...
- vue获取table一列数据_VUE table表格动态添加一列数据,新增的这些数据不可以编辑(v-model绑定的数据不能实时更新)...
一.问题 用elementUi横着增加一行数据没毛病,可以操作 添加一列,这新增的这一列, 第一次去赋值的时候值是改了, 但没生效 点击下一行时 值就变过来 二.原因 横向添加 是复制上面的某一条数据 ...
- 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 ...
- Springboot整合Nacos配置中心
前提 安装了Nacos服务端并登陆到其控制台页面. 创建一个Springboot工程. Nacos配置:(相关概念后面说明): 创建一个命名空间,当然,也有默认的命名空间. 创建配置文件: 整合 第一 ...
- 获取nacos配置中心文件值_Java 静态(static)方法读取 Nacos 配置中心
为了降成本节省服务器资源,需要将配置中心从 Apollo 切换到 Nacos,因为注册中心是 Nacos 直接复用集群即可. 问题描述 util 里面获取配置的方法都是 static 静态方法,这就导 ...
- 关于IE缓存所带来的数据不能实时更新的解决办法
IE缓存 为了提高访问网页的速度,IE会采用一种缓存机制,将你访问过的网页内容存在电脑里,然后当我们每次访问网站时,首先就会搜索有没有这些内容,如果有就直接从缓存中调出来,从而提高访问网站的速度,这本 ...
- springboot读取bootstrap配置及knife4j版本兼容性问题
文章目录 项目框架说明 连环坑 问题一:springboot项目无法读取bootstrap.yml配置 问题二:启动报错,springboot与springcloud版本不匹配 问题三:启动报错Fai ...
最新文章
- 功能演示:戴尔PowerConnect 8024交换机VLAN的创建与删除
- ie 6 对注释标记的一个不稳定的错误
- java中的volatile和synchronized
- Python数据结构与算法(第二天)
- js解析二维码_最新最全阿里巴巴,今日头条,腾讯Flutter面试真题全解析(狂虐不止)...
- 08-百度ai语音合成
- vuecli启动的服务器位置,在vue cli 3生成的项目中启动dev服务器
- 第三次学JAVA再学不好就吃翔(part71)--BigDecimal类
- 微软发布 SQL Server 2019 新版本
- ExtJs UI框架学习六
- Android图片上传和下载,android 上传/下载 图片
- 教育|仝卓高考舞弊案细节曝光:为“恢复高考成绩”已起诉1年多
- xcode UIView常用方法属性动画
- 使用海康H5视频播放器开发包实现监控播放
- 如何做好数据分析的数据采集工作?
- 你真的会写for循环吗?来看看这些常见的for循环优化方式
- 箱形图适用于哪种数据_Excel 数据可视化:箱形图全面解析!
- Nginx反向代理与负载均衡应用实践(二)
- java 链表 置为null_Java: 链表head取出用后,置next=null为何可以加速gc?
- ipv4与ipv6的联系与区别