Nacos 手动实现配置热更新
Spring Cloud 提供了 @RefreshScope 注解来自动刷新配置,但在某些场景下需要手动监听 Nacos 配置变更,比如从 Nacos 拉取一个完整的文本文件内容(如 AI 系统提示词),这种非标准 key-value 的配置用 @RefreshScope 不太好处理。本文介绍如何通过 Nacos 原生 API 手动实现配置热更新。
核心 API
Nacos 客户端提供了两个关键方法:
// 启动时主动拉取配置
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-id: system-chat-message.txt
group: DEFAULT_GROUP
timeout-ms: 20000
@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 中的 data-id 会自动对应 Java 的 dataId(kebab-case 转 camelCase)。
热更新实现
@Slf4j
@Getter
@Configuration
@RequiredArgsConstructor
public class SystemPromptConfig {
private final NacosConfigManager nacosConfigManager;
private final AIProperties aiProperties;
// 使用 AtomicReference 保证线程安全
private final AtomicReference<String> chatSystemMessage = new AtomicReference<>();
@PostConstruct
public void init() {
loadConfig(aiProperties.getSystem().getChat(), chatSystemMessage);
}
private void loadConfig(AIProperties.System.Chat chatConfig, AtomicReference<String> 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 回调,将内存中的值更新为新内容。全程无需重启服务。
为什么用 AtomicReference
Spring Boot 的 Web 服务基于 Tomcat 线程池,多个请求线程同时读取 chatSystemMessage,而 Nacos 的监听回调运行在独立的线程上,负责写入新值。这是典型的多线程并发读写同一个共享变量的场景。
AtomicReference 底层基于 CPU 的 CAS(Compare-And-Swap)原子指令,保证 get() 和 set() 操作是原子性的——要么读到旧值,要么读到新值,不会读到写了一半的中间状态,也不需要加锁。
// 线程A(Tomcat工作线程):读取提示词
String prompt = systemPromptConfig.getChatSystemMessage().get();
// 线程B(Nacos回调线程):更新提示词
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,简单高效。当配置内容不是标准的属性格式(如完整的文本文件),或者需要在配置变更时执行额外的处理逻辑时,再考虑手动监听的方式。
更多推荐




所有评论(0)