文章目录
配置中心是微服务架构的基础设施,Nacos 是当前最流行的选择之一。面试官问"Nacos 配置更新的工作流程",他想听的是:你在 Nacos 控制台改了一个配置,这个变更是怎么推送到所有服务的?长轮询是什么?为什么不直接推送?
先说结论
| 维度 | 说明 |
|---|---|
| 通信方式 | 长轮询(Long Polling) |
| 客户端拉取 | 启动时全量拉取 + 运行时长轮询监听 |
| 服务端推送 | 不是真正推送,是长轮询挂起后立即返回 |
| 一致性 | CP 模式(Raft 协议),保证配置强一致 |
一句话记住:Nacos 配置更新像取快递------你定期去快递柜看看有没有你的包裹(长轮询),有就取走;快递到了你就不用等了(挂起后立即返回)
完整工作流程
步骤一:启动时全量拉取
服务启动时,从 Nacos Server 拉取所有需要的配置:
yaml
# bootstrap.yml
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: dev
shared-configs:
- data-id: common.yml # 👈 共享配置
refresh: true
java
// Spring Cloud 启动时自动拉取
// NacosPropertySourceLocator.loadPropertySource()
String config = nacosClient.getConfig(dataId, group, 3000); // 👈 全量拉取
步骤二:注册监听器
配置拉取到本地后,客户端注册 Listener 监听配置变化:
java
nacosClient.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
// 👈 配置变化时的回调
// 更新 Spring Environment 中的属性值
// 触发 @RefreshScope Bean 的重建
}
});
步骤三:长轮询监听变化
客户端启动 长轮询 线程,定期向 Nacos Server 发送检查请求:
客户端 Nacos Server
│ │
│── POST /listener ───────────────────→ │
│ (携带 dataId + md5) │
│ │ 比较 md5
│ │ 如果没变化 → 挂起 29.5秒
│ │ 如果有变化 → 立即返回
│←───── 响应(变化的 dataId)──────────── │
│ │
│── GET /configs ─────────────────────→ │
│ (拉取最新配置) │
│←───── 最新配置内容 ─────────────────── │
│ │
│ 触发 Listener 回调 │
│ 更新本地配置 │
长轮询的细节
客户端请求:
http
POST /nacos/v1/cs/configs/listener
Content-Type: application/x-www-form-urlencoded
dataId+group+md5
服务端处理:
java
// 简化逻辑
public String doPolling(HttpServletRequest req) {
String clientMd5 = req.getParameter("Listening-Configs");
String serverMd5 = getServerMd5(dataId, group);
if (clientMd5.equals(serverMd5)) {
// 👈 配置没变,挂起 29.5 秒
asyncContext.timeout(29500);
// 挂起期间如果配置变化,立即返回
} else {
// 👈 配置变了,立即返回变化的 dataId
return changedDataId;
}
}
为什么不直接推送?
| 方式 | 优点 | 缺点 |
|---|---|---|
| 推送(WebSocket) | 实时性好 | 服务端维护连接开销大,扩展性差 |
| 长轮询 | 实现简单,扩展性好 | 有 29.5 秒的最大延迟 |
| 短轮询 | 最简单 | 浪费带宽,延迟高 |
Nacos 选择长轮询是 在实时性和复杂度之间的折中。配置变更的实时性要求不像消息推送那么高,30 秒内的延迟可以接受。
步骤四:配置更新生效
客户端收到新配置后:
java
// 1. 触发 Listener 回调
listener.receiveConfigInfo(newConfig);
// 2. 更新 Spring Environment
environment.getPropertySources().replace("nacos", newPropertySource);
// 3. 刷新 @RefreshScope Bean
refreshScope.refresh("myBean"); // 👈 带 @RefreshScope 的 Bean 会被重建
@RefreshScope 的原理:标注了 @RefreshScope 的 Bean 会被 CGLIB 代理包装,配置变更时代理会销毁旧实例、创建新实例(读取最新配置值)。
Nacos Server内部的数据同步
多个 Nacos Server 节点之间用 Raft 协议 保证配置数据的强一致性:
写请求 → Leader 节点 → 半数以上 Follower 确认 → 写入成功
读请求 → 任意节点(可能读 Follower)
配置数据存在内嵌的 Derby 数据库(集群模式用 MySQL),Raft 协议保证所有节点的数据一致。
Nacos 配置更新全景
客户端流程
├── 启动全量拉取配置
├── 注册 Listener 监听变化
├── 长轮询检测变化(29.5秒超时)
├── 检测到变化 → 拉取最新配置
├── 触发 Listener 回调
└── 更新 Spring Environment + @RefreshScope
长轮询机制
├── 客户端发送 dataId + md5
├── md5 一致 → 挂起等待
├── md5 不一致 → 立即返回
└── 折中方案:实时性 + 简单性
服务端一致性
├── Raft 协议保证 CP
├── Leader 写入,Follower 复制
└── 内嵌 Derby / 外置 MySQL
口诀:启动拉取全量配,Listener注册来监听;
长轮询挂二十九,md5不同立刻回;
变更拉取新配置,RefreshScope来刷新;
Raft保证强一致,CP模型不丢配
回答技巧与点评
标准回答:Nacos 配置更新的工作流程:服务启动时全量拉取配置并注册 Listener,运行时通过长轮询机制监听配置变化。客户端定期发送 dataId + md5 到服务端,如果 md5 不一致则立即返回变化信息,一致则挂起最多 29.5 秒。检测到变化后客户端拉取最新配置,触发 Listener 回调更新 Spring Environment 和 @RefreshScope Bean。Nacos Server 内部通过 Raft 协议保证配置数据的强一致性。
加分回答
- 长轮询 vs 长连接:Nacos 1.x 使用长轮询,Nacos 2.x 引入了 gRPC 长连接,配置变更可以通过长连接实时推送,延迟更低。但长轮询作为降级方案仍然保留
- @RefreshScope 的坑:@RefreshScope 会导致 Bean 被代理,代理对象在首次访问时才创建。如果刷新后 Bean 创建失败,会导致服务不可用------生产环境慎用
- 灰度发布:Nacos 支持 Beta 配置发布,可以指定 IP 让部分实例先收到新配置,验证没问题再全量发布,降低配置变更的风险
面试官点评
这道题考的是你对 配置中心原理 的理解。能说出长轮询机制算及格,高分的关键在于:解释为什么选长轮询而不是推送 (实时性和复杂度的权衡),以及 @RefreshScope 的工作原理(代理 + 重建)。如果能提到 Nacos 2.x 的 gRPC 改进,说明你关注技术演进。
内容有帮助?点赞、收藏、关注三连!评论区等你 💪