Nacos-SpringBoot 配置无法自动刷新问题排查

背景

Nacos SpringBoot版本中,提供了@NacosValue注解,支持控制台修改值时,自动刷新,但是今天遇见了无法自动刷新的问题。

环境

SpringBoot 2.2.x
nacos-client:2.1.0
nacos-config-spring-boot-starter:0.2.12

问题排查

首先确认,nacos的配置信息:

nacos:config:bootstrap:enable: truelog-enable: falseserver-addr: xxxtype: yamlauto-refresh: truedata-ids: my-config.yml

确认auto-refresh配置为true,Nacos提供了注解@NacosConfigListener可以监听配置修改的信息,排查nacos与client的长连接通道是否正常。

@NacosConfigListener(dataId = "${nacos.config.data-ids}", timeout = 5000)
public void onConfigChange(String newConfig) {log.info("Nacos配置更新完成, config data={}", newConfig);
}

在控制台修改配置,发现onConfigChange()可以正常触发,说明长连通道没有问题,看来只能追查源码。

com.alibaba.nacos.client.config.impl.CacheData#safeNotifyListener

private void safeNotifyListener(final String dataId, final String group, final String content, final String type,final String md5, final String encryptedDataKey, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;if (listenerWrap.inNotifying) {LOGGER.warn("[{}] [notify-currentSkip] dataId={}, group={}, md5={}, listener={}, listener is not finish yet,will try next time.",name, dataId, group, md5, listener);return;}Runnable job = () -> {long start = System.currentTimeMillis();ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener) listener;adapter.fillContext(dataId, group);LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);}// Before executing the callback, set the thread classloader to the classloader of// the specific webapp to avoid exceptions or misuses when calling the spi interface in// the callback method (this problem occurs only in multi-application deployment).Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listenerWrap.inNotifying = true;// 重点实现方法,将Nacos Server的配置写入Spring容器中listener.receiveConfigInfo(contentTmp);// compare lastContent and contentif (listener instanceof AbstractConfigChangeListener) {Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);ConfigChangeEvent event = new ConfigChangeEvent(data);((AbstractConfigChangeListener) listener).receiveConfigChange(event);listenerWrap.lastContent = content;}.....省略部分代码}

进入receiveConfigInfo()实现:
com.alibaba.nacos.spring.context.event.config.DelegatingEventPublishingListener#receiveConfigInfo

@Override
public void receiveConfigInfo(String content) {// 刷新Spring容器配置onReceived(content);// 发布变更事件publishEvent(content);
}

com.alibaba.nacos.spring.core.env.NacosPropertySourcePostProcessor#addListenerIfAutoRefreshed

public static void addListenerIfAutoRefreshed(final NacosPropertySource nacosPropertySource, final Properties properties,final ConfigurableEnvironment environment) {if (!nacosPropertySource.isAutoRefreshed()) { // Disable Auto-Refreshedreturn;}final String dataId = nacosPropertySource.getDataId();final String groupId = nacosPropertySource.getGroupId();final String type = nacosPropertySource.getType();final NacosServiceFactory nacosServiceFactory = getNacosServiceFactoryBean(beanFactory);try {ConfigService configService = nacosServiceFactory.createConfigService(properties);Listener listener = new AbstractListener() {@Overridepublic void receiveConfigInfo(String config) {String name = nacosPropertySource.getName();NacosPropertySource newNacosPropertySource = new NacosPropertySource(dataId, groupId, name, config, type);newNacosPropertySource.copy(nacosPropertySource);MutablePropertySources propertySources = environment.getPropertySources();// replace NacosPropertySource// 核心实现,将Nacos的配置值刷新Spring容器中的配置值propertySources.replace(name, newNacosPropertySource);}};....省略部分代码}

点击replace的实现,发现一点问题:
疑点1
这里面有两个实现,其中一个是jasypt的实现,这个三方类库是常用于对代码中的数据库配置信息进行加密的,难道说,是因为它?同时在控制台也有一条日志值得注意:

[notify-error] dataId=xxx  …… placeholder 'project.version' in value "${project.version}

这里是一个疑点,我们先继续往下看:
com.alibaba.nacos.spring.context.event.config.DelegatingEventPublishingListener#receiveConfigInfo

@Override
public void receiveConfigInfo(String content) {onReceived(content);publishEvent(content);
}private void publishEvent(String content) {NacosConfigReceivedEvent event = new NacosConfigReceivedEvent(configService,dataId, groupId, content, configType);// 发布变更事件applicationEventPublisher.publishEvent(event);
}

org.springframework.context.support.AbstractApplicationContext#publishEvent

/*** Publish the given event to all listeners.* @param event the event to publish (may be an {@link ApplicationEvent}* or a payload object to be turned into a {@link PayloadApplicationEvent})* @param eventType the resolved event type, if known* @since 4.2*/protected void publishEvent(Object event, @Nullable ResolvableType eventType) {Assert.notNull(event, "Event must not be null");// Decorate event as an ApplicationEvent if necessaryApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;}else {applicationEvent = new PayloadApplicationEvent<>(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// Multicast right now if possible - or lazily once the multicaster is initializedif (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {// 重点关注,发布广播事件,通知全部监听器getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);}else {this.parent.publishEvent(event);}}}

下面就是核心的实现部分,真正刷新值的实现逻辑:
com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor#onApplicationEvent

@Override
public void onApplicationEvent(NacosConfigReceivedEvent event) {// In to this event receiver, the environment has been updated the// latest configuration information, pull directly from the environment// fix issue #142for (Map.Entry<String, List<NacosValueTarget>> entry : placeholderNacosValueTargetMap.entrySet()) {String key = environment.resolvePlaceholders(entry.getKey());// 从Spring容器中获取变更后的新值,通过反射的方式,更新数据String newValue = environment.getProperty(key);if (newValue == null) {continue;}List<NacosValueTarget> beanPropertyList = entry.getValue();for (NacosValueTarget target : beanPropertyList) {String md5String = MD5Utils.md5Hex(newValue, "UTF-8");boolean isUpdate = !target.lastMD5.equals(md5String);if (isUpdate) {target.updateLastMD5(md5String);Object evaluatedValue = resolveNotifyValue(target.nacosValueExpr, key, newValue);if (target.method == null) {setField(target, evaluatedValue);}else {setMethod(target, evaluatedValue);}}}}
}

OK,看到这里,基本已经明朗,了解了Nacos配置刷新的全流程,非常可疑的一点,就是三方类库jasypt,为了验证才想,我们将jasypt的类库移除,再次进行尝试,奇迹出现了!Nacos可以顺利刷新配置值,终于破案,是因为jasypt的加密导致的该问题,搜了一下可能导致的原因:
1、属性源优先级冲突:jasypt 的 PropertySource(如 EncryptablePropertySource)可能覆盖或干扰 Nacos 的动态属性源,导致解密后的值无法被 Nacos 更新逻辑捕获。
2、解密逻辑未触发:Nacos 配置更新时,新的加密值未被 jasypt 及时解密,导致 Environment 中仍是旧值。

查看一下jasypt使用的版本:

<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.1</version>
</dependency>

猜想是否是2.1.1版本的BUG导致了该问题,于是升级至最新版本3.0.5,再次进行测试,发现Nacos可以顺利更新,问题解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA | 聚焦 OutOfMemoryError 异常

个人主页 文章专栏 在正文开始前&#xff0c;我想多说几句&#xff0c;也就是吐苦水吧…最近这段时间一直想写点东西&#xff0c;停下来反思思考一下。 心中万言&#xff0c;真正执笔时又不知先写些什么。通常这个时候&#xff0c;我都会随便写写&#xff0c;文风极像散文&…

基于开源技术体系的品牌赛道力重构:AI智能名片与S2B2C商城小程序源码驱动的品类创新机制研究

摘要&#xff1a;在数字经济与实体经济深度融合的背景下&#xff0c;品牌竞争已从单一产品力竞争转向生态化、技术化的赛道力竞争。本文以开源AI大模型、AI智能名片及S2B2C商城小程序源码为核心技术载体&#xff0c;构建"技术赋能-场景贯通-生态协同"三维分析框架&am…

【vue3】购物车实战:从状态管理到用户体验的全流程实现

在电商项目中&#xff0c;购物车是核心功能之一&#xff0c;需要兼顾数据一致性、用户体验和逻辑复杂度。 本文结合 Vue3 Pinia 技术栈&#xff0c;详细讲解如何实现一个高效且易用的购物车系统&#xff0c;重点剖析 添加购物车 和 头部购物车预览 的核心逻辑与实现细节。 一…

卡洛诗西餐厅,以“中式西餐”为核心战略

在餐饮市场的激烈竞争中&#xff0c;“本土化”是许多国际餐饮品牌难以跨越的鸿沟——要么因水土不服黯然退场&#xff0c;要么因过度妥协失去特色。然而&#xff0c;卡洛诗以“中式西餐”为核心战略&#xff0c;将西餐与国内饮食文化深度融合&#xff0c;不仅破解了西餐本土化…

28-29【动手学深度学习】批量归一化 + ResNet

1. 批量归一化 1.1 原理 当神经网络比较深的时候会发现&#xff1a;数据在下面&#xff0c;损失函数在上面&#xff0c;这样会出现什么问题&#xff1f; 正向传递的时候&#xff0c;数据是从下往上一步一步往上传递反向传递的时候&#xff0c;数据是从上面往下传递&#xff0…

【Linux网络】Http服务优化 - 增加请求后缀、状态码描述、重定向、自动跳转及注册多功能服务

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

AIGC(生成式AI)试用 32 -- AI做软件程序测试 3

总结之前的AI做程序测试过程&#xff0c;试图优化提问方式&#xff0c;整合完成的AI程序测试提问&#xff0c;探索更多可能的AI测试 AIGC&#xff08;生成式AI&#xff09;试用 30 -- AI做软件程序测试 1 AIGC&#xff08;生成式AI&#xff09;试用 31 -- AI做软件程序…

C语言实现迪杰斯特拉算法进行路径规划

使用C语言实现迪杰斯特拉算法进行路径规划 迪杰斯特拉算法是一种用于寻找加权图中最短路径的经典算法。它特别适合用于计算从一个起点到其他所有节点的最短路径&#xff0c;前提是图中的边权重为非负数。 一、迪杰斯特拉算法的基本原理 迪杰斯特拉算法的核心思想是“贪心法”…

引领印尼 Web3 变革:Mandala Chain 如何助力 1 亿用户迈向数字未来?

当前 Web3 的发展正处于关键转折点&#xff0c;行业亟需吸引新用户以推动 Web3 的真正大规模采用。然而&#xff0c;大规模采用面临着核心挑战&#xff1a;数据泄露风险、集中存储的安全漏洞、跨系统互操作性障碍&#xff0c;以及低效的服务访问等问题。如何才能真正突破这些瓶…

WebSocket是h5定义的,双向通信,节省资源,更好的及时通信

浏览器和服务器之间的通信更便利&#xff0c;比http的轮询等效率提高很多&#xff0c; WebSocket并不是权限的协议&#xff0c;而是利用http协议来建立连接 websocket必须由浏览器发起请求&#xff0c;协议是一个标准的http请求&#xff0c;格式如下 GET ws://example.com:3…

Kaamel白皮书:IoT设备安全隐私评估实践

1. IoT安全与隐私领域的现状与挑战 随着物联网技术的快速发展&#xff0c;IoT设备在全球范围内呈现爆发式增长。然而&#xff0c;IoT设备带来便捷的同时&#xff0c;也引发了严峻的安全与隐私问题。根据NSF&#xff08;美国国家科学基金会&#xff09;的研究表明&#xff0c;I…

php安装swoole扩展

PHP安装swoole扩展 Swoole官网 安装准备 安装前必须保证系统已经安装了下列软件 4.8 版本需要 PHP-7.2 或更高版本5.0 版本需要 PHP-8.0 或更高版本6.0 版本需要 PHP-8.1 或更高版本gcc-4.8 或更高版本makeautoconf 安装Swool扩展 安装官方文档安装后需要再php.ini中增加…

服务器传输数据存储数据建议 传输慢的原因

一、JSON存储的局限性 1. 性能瓶颈 全量读写&#xff1a;JSON文件通常需要整体加载到内存中才能操作&#xff0c;当数据量大时&#xff08;如几百MB&#xff09;&#xff0c;I/O延迟和内存占用会显著增加。 无索引机制&#xff1a;查找数据需要遍历所有条目&#xff08;时间复…

Android四大核心组件

目录 一、为什么需要四大组件&#xff1f; 二、Activity&#xff1a;看得见的界面 核心功能 生命周期图解 代码示例 三、Service&#xff1a;看不见的劳动者 两大类型 生命周期对比 注意陷阱 四、BroadcastReceiver&#xff1a;消息传递专员 两种注册方式 广播类型 …

「Mac畅玩AIGC与多模态01」架构篇01 - 展示层到硬件层的架构总览

一、概述 AIGC&#xff08;AI Generated Content&#xff09;系统由多个结构层级组成&#xff0c;自上而下涵盖交互界面、API 通信、模型推理、计算框架、底层驱动与硬件支持。本篇梳理 AIGC 应用的六层体系结构&#xff0c;明确各组件在系统中的职责与上下游关系&#xff0c;…

[MERN 项目实战] MERN Multi-Vendor 电商平台开发笔记(v2.0 从 bug 到结构优化的工程记录)

[MERN 项目实战] MERN Multi-Vendor 电商平台开发笔记&#xff08;v2.0 从 bug 到结构优化的工程记录&#xff09; 其实之前没想着这么快就能把 2.0 的笔记写出来的&#xff0c;之前的预期是&#xff0c;下一个阶段会一直维持到将 MERN 项目写完&#xff0c;毕竟后期很多东西都…

互斥量函数组

头文件 #include <pthread.h> pthread_mutex_init 函数原型&#xff1a; int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 函数参数&#xff1a; mutex&#xff1a;指向要初始化的互斥量的指针。 attr&#xf…

互联网的下一代脉搏:深入理解 QUIC 协议

互联网的下一代脉搏&#xff1a;深入理解 QUIC 协议 互联网是现代社会的基石&#xff0c;而数据在其中高效、安全地传输是其运转的关键。长期以来&#xff0c;传输层的 TCP&#xff08;传输控制协议&#xff09;一直是互联网的主力军。然而&#xff0c;随着互联网应用场景的日…

全球城市范围30米分辨率土地覆盖数据(1985-2020)

Global urban area 30 meter resolution land cover data (1985-2020) 时间分辨率年空间分辨率10m - 100m共享方式保护期 277 天 5 时 42 分 9 秒数据大小&#xff1a;8.98 GB数据时间范围&#xff1a;1985-2020元数据更新时间2024-01-11 数据集摘要 1985~2020全球城市土地覆…

【Vue】单元测试(Jest/Vue Test Utils)

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Vue 文章目录 1. Vue 单元测试简介1.1 为什么需要单元测试1.2 测试工具介绍 2. 环境搭建2.1 安装依赖2.2 配置 Jest 3. 编写第一个测试3.1 组件示例3.2 编写测试用例3.3 运行测试 4. Vue Test Utils 核心 API4.1 挂载组件4.2 常…