Nacos 手动实现配置热更新
Spring Cloud 提供了 RefreshScope 注解来自动刷新配置但在某些场景下需要手动监听 Nacos 配置变更比如从 Nacos 拉取一个完整的文本文件内容如 AI 系统提示词这种非标准 key-value 的配置用 RefreshScope 不太好处理。本文介绍如何通过 Nacos 原生 API 手动实现配置热更新。核心 APINacos 客户端提供了两个关键方法// 启动时主动拉取配置 String config configService.getConfig(dataId, group, timeoutMs); // 注册监听器配置变更时 Nacos 主动推送新内容 configService.addListener(dataId, group, new Listener() { public void receiveConfigInfo(String configInfo) { // configInfo 就是变更后的新配置内容 } });底层原理是 Nacos 客户端与服务端之间维持长轮询连接服务端检测到配置变更时主动推送给客户端触发 receiveConfigInfo 回调。配置属性类首先定义配置属性类将 yml 中的配置映射为 Java 对象tj: ai: prompt: system: chat: >Data Configuration ConfigurationProperties(prefix tj.ai.prompt) public class AIProperties { private System system; Data public static class System { private Chat chat; Data public static class Chat { private String dataId; private String group DEFAULT_GROUP; private long timeoutMs 20000L; } } }ConfigurationProperties 将 yml 中 tj.ai.prompt 前缀下的配置按层级映射到嵌套的静态内部类字段上。yml 中的>Slf4j Getter Configuration RequiredArgsConstructor public class SystemPromptConfig { private final NacosConfigManager nacosConfigManager; private final AIProperties aiProperties; // 使用 AtomicReference 保证线程安全 private final AtomicReferenceString chatSystemMessage new AtomicReference(); PostConstruct public void init() { loadConfig(aiProperties.getSystem().getChat(), chatSystemMessage); } private void loadConfig(AIProperties.System.Chat chatConfig, AtomicReferenceString target) { try { var dataId chatConfig.getDataId(); var group chatConfig.getGroup(); var timeoutMs chatConfig.getTimeoutMs(); // 第一步启动时从 Nacos 拉取配置 var config nacosConfigManager.getConfigService() .getConfig(dataId, group, timeoutMs); target.set(config); log.info(读取配置成功内容为{}, config); // 第二步注册监听器配置变更时自动更新 nacosConfigManager.getConfigService() .addListener(dataId, group, new Listener() { Override public Executor getExecutor() { return null; } Override public void receiveConfigInfo(String info) { target.set(info); log.info(配置更新成功新内容为{}, info); } }); } catch (Exception e) { log.error(加载配置失败, e); } } }流程说明整个热更新分为两个阶段启动阶段PostConstruct 标注的 init() 方法在 Spring 创建 Bean 后立即执行调用 getConfig() 从 Nacos 拉取配置内容存入 AtomicReference。运行阶段addListener() 注册了一个监听器。当在 Nacos 控制台修改配置并发布后Nacos 服务端通过长轮询检测到变更主动推送新内容给客户端触发 receiveConfigInfo 回调将内存中的值更新为新内容。全程无需重启服务。为什么用 AtomicReferenceSpring Boot 的 Web 服务基于 Tomcat 线程池多个请求线程同时读取 chatSystemMessage而 Nacos 的监听回调运行在独立的线程上负责写入新值。这是典型的多线程并发读写同一个共享变量的场景。AtomicReference 底层基于 CPU 的 CASCompare-And-Swap原子指令保证 get() 和 set() 操作是原子性的——要么读到旧值要么读到新值不会读到写了一半的中间状态也不需要加锁。// 线程ATomcat工作线程读取提示词 String prompt systemPromptConfig.getChatSystemMessage().get(); // 线程BNacos回调线程更新提示词 chatSystemMessage.set(newContent);使用方式在业务代码中注入 SystemPromptConfig随时获取最新的配置内容Autowired private SystemPromptConfig systemPromptConfig; public void callAI() { String systemMessage systemPromptConfig.getChatSystemMessage().get(); // 使用 systemMessage 调用大模型... }在 Nacos 控制台修改 system-chat-message.txt 的内容并发布后下次调用 get() 拿到的就是更新后的值无需重启服务。与 RefreshScope 的对比RefreshScope手动 addListener适用场景yml 中的普通 key-value 配置文本文件、需要额外处理的配置实现复杂度加一个注解即可需要手动编写加载和监听逻辑灵活性低自动映射高可自定义处理逻辑线程安全Spring 自动处理需自行保证如 AtomicReference普通配置项优先使用 RefreshScope简单高效。当配置内容不是标准的属性格式如完整的文本文件或者需要在配置变更时执行额外的处理逻辑时再考虑手动监听的方式。