Nacos 的 Push 机制 是其 配置中心(Config Server) 实现 实时配置推送 的核心功能。当用户通过 Nacos 控制台或 API 发布/更新配置时,Nacos Server 会主动将变更推送给所有订阅该配置的客户端,从而实现毫秒级的配置热更新。
本文将从 源码角度 深入解析 Nacos 的 Push 机制,涵盖:
- Push 的整体流程
- 服务端如何识别订阅者
- 推送任务的异步执行
- 客户端如何接收与确认
- 失败重试与可靠性保障
一、Push 机制整体流程
1. 用户发布配置 → Nacos Server
2. Server 持久化配置(DB + Raft)
3. 触发 Push 任务
4. 查找订阅了该 dataId + group + tenant 的所有客户端连接
5. 通过长连接(HTTP Streaming)推送变更通知
6. 客户端收到后拉取最新配置
7. 客户端返回 ACK 确认
8. Server 收到 ACK,标记推送成功
9. 若超时未收到 ACK,加入重试队列
✅ Push 是“通知型”推送:只推“变更通知”,不推完整配置内容。客户端收到通知后主动调用
GET /nacos/v1/cs/configs拉取最新值。
二、关键类与包结构
Nacos Push 相关源码位于:
com.alibaba.nacos.config.server
├── controller.ConfigController // 配置写入入口
├── service.ConfigServletInner // 核心服务逻辑
├── push.PushService // 推送核心服务
├── push.ClientAgent // 客户端连接管理
├── push.PushTask // 推送任务
└── remote.RpcPushService // 支持 gRPC 推送(1.4+)
三、Push 触发源码详解
1. 配置发布入口:ConfigController#publishConfig
// com.alibaba.nacos/config/server/controller/ConfigController.java
@PostMapping("/publishConfig")
public Boolean publishConfig(...) {
// 调用 ConfigServletInner 执行发布
configService.publishConfig(dataId, group, tenant, content, ...);
return true;
}
2. 核心发布逻辑:ConfigServletInner#publishConfig
// com.alibaba.nacos/config/server/service/ConfigServletInner.java
public boolean publishConfig(...) {
// 1. 写入数据库
persistService.insertOrUpdate(tenant, dataId, group, content, ...);
// 2. 如果是持久化配置,触发 Raft 同步(CP 模式)
if (isUseRaftForConfig()) {
raftConfigMultiImpl.onChange(tenant, group, dataId, content);
}
// 3. 触发推送
ConfigTraceService.logNotifyEvent(dataId, group, tenant, null, targetIP, 0, System.currentTimeMillis());
NotifyCenter.publishEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, time, requestIp, notifyClientType));
return true;
}
NotifyCenter.publishEvent()是 Push 的事件触发点。
四、Push 事件监听:PushService 监听 ConfigDataChangeEvent
// com.alibaba.nacos/config/server/push/PushService.java
@NacosAsyncExecute
@EventListener
public void onEvent(ConfigDataChangeEvent event) {
// 构造推送任务
PushTask task = new PushTask();
task.setDataId(event.getDataId());
task.setGroup(event.getGroup());
task.setTenant(event.getTenant());
task.setTarget(event.getTarget());
task.setLastModified(event.getLastModified());
// 提交到任务队列
serviceManager.enqueueTask(task);
}
@EventListener监听配置变更事件。serviceManager.enqueueTask(task)将任务加入内存队列。
五、Push 任务执行:PushService$PushTask
class PushTask implements Runnable {
@Override
public void run() {
String dataId = getDataId();
String group = getGroup();
String tenant = getTenant();
// 查询订阅该配置的所有客户端
List<ClientInfo> subscribers = subscriberRegistry.getSubscribers(dataId, group, tenant);
for (ClientInfo client : subscribers) {
String ip = client.getIp();
int port = client.getClientPort(); // 客户端上报的监听端口(默认 8088)
// 构造推送内容
Map<String, String> notify = new HashMap<>();
notify.put("dataId", dataId);
notify.put("group", group);
notify.put("tenant", tenant);
notify.put("type", "config");
notify.put("lastModified", String.valueOf(lastModified));
// 异步推送
HttpAsync.sendPost("http://" + ip + ":" + port + "/nacos/v1/cs/notify", notify, new PushCallback() {
@Override
public void onSuccess() {
Loggers.PUSH.info("Push OK, dataId={}, group={}, ip={}", dataId, group, ip);
}
@Override
public void onFail() {
// 加入重试队列
retryManager.addRetryTask(new RetryTask(dataId, group, tenant, ip, port));
}
});
}
}
}
关键点解析:
- 订阅者查询:
subscriberRegistry.getSubscribers()从内存中获取订阅关系。 - 推送目标端口:客户端启动时会通过
/nacos/v1/cs/clientInfo上报自己的notifyPort(默认 8088)。 - HTTP 推送:发送 POST 请求到客户端的
/nacos/v1/cs/notify接口。 - 异步非阻塞:使用
HttpAsync避免阻塞主线程。
六、客户端接收推送:ConfigController#notify
// 客户端收到推送通知
@GetMapping("/nacos/v1/cs/notify")
public String notify(@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam(value = "tenant", required = false) String tenant) {
// 触发本地配置拉取
ConfigRpcService.getInstance().notifyConfigInfo(dataId, group, tenant);
return "OK";
}
- 客户端收到通知后,立即调用
notifyConfigInfo()去服务端拉取最新配置。 - 拉取逻辑:
GET /nacos/v1/cs/configs?dataId=xxx&group=xxx&tenant=xxx
七、订阅关系管理
1. 客户端订阅配置
当客户端首次调用 getConfig(dataId, group) 时,服务端会记录其订阅关系。
// ConfigServletInner#getConfig
public String getConfig(...) {
// 记录订阅关系
clientManager.registerListener(dataId, group, tenant, clientInfo);
return content;
}
clientManager.registerListener()将客户端加入subscriberRegistry。- 订阅关系保存在内存中(
ConcurrentHashMap)。
2. 订阅关系结构
Map<String/*groupKey*/, Set<ClientInfo>> subscriberMap = new ConcurrentHashMap<>();
groupKey = dataId + "||" + group + "||" + tenant- 每个配置对应一个客户端集合
八、可靠性保障:失败重试机制
1. 重试管理器:RetryManager
public class RetryManager {
private final BlockingQueue<RetryTask> retryQueue = new LinkedBlockingQueue<>();
public void addRetryTask(RetryTask task) {
retryQueue.offer(task);
}
// 后台线程消费重试队列
@PostConstruct
public void start() {
GlobalExecutor.submitRetryTask(new Runnable() {
@Override
public void run() {
RetryTask task = retryQueue.take();
// 指数退避重试
int delay = (int) Math.pow(2, task.getRetryCount());
Thread.sleep(delay * 1000);
// 重新执行推送
pushService.repush(task);
}
});
}
}
- 支持最多 3 次重试。
- 采用 指数退避 策略,避免雪崩。
九、gRPC Push(Nacos 1.4+ 新特性)
从 Nacos 1.4 开始,支持基于 gRPC 的长连接推送,替代传统的 HTTP 轮询和短连接 Push。
特点:
- 客户端与服务端建立 gRPC 长连接
- 服务端通过 Stream 主动推送
- 更高效、更低延迟、更可靠
// RpcPushService.java
public class RpcPushService implements PushService {
public void pushConfigChange(ConfigChangeRequest request, Connection conn) {
conn.send(request);
}
}
- 客户端通过
GrpcClient连接服务端。 - 服务端通过
ConnectionManager管理所有 gRPC 连接。
🚀 推荐生产环境使用 gRPC 模式,性能更好。
十、源码调用链总结
ConfigController.publishConfig()
→ ConfigServletInner.publishConfig()
→ NotifyCenter.publishEvent(ConfigDataChangeEvent)
→ PushService.onEvent()
→ PushTask.run()
→ subscriberRegistry.getSubscribers()
→ HttpAsync.sendPost("http://client:8088/notify")
← 客户端收到 notify
→ 客户端拉取最新配置
十一、关键配置项
| 配置项 | 说明 |
|---|---|
nacos.config.notify.port | 客户端监听推送的端口,默认 8088 |
nacos.push.enabled | 是否启用 Push,默认 true |
nacos.push.retry.max | 最大重试次数,默认 3 |
nacos.raft.use-grpc | 是否启用 gRPC 推送(1.4+) |
十二、总结
| 特性 | 实现方式 |
|---|---|
| Push 触发 | 配置变更 → 事件总线 → PushTask |
| 订阅管理 | 内存 Map 保存 dataId+group → 客户端列表 |
| 推送方式 | HTTP POST 到客户端 notifyPort |
| 可靠性 | ACK + 失败重试 + 指数退避 |
| 高性能 | 异步非阻塞 + 批量任务 |
| 新版本优化 | 支持 gRPC 长连接推送 |
建议阅读源码路径
PushService.java—— 推送核心PushTask.java—— 推送任务ConfigServletInner.java—— 发布与推送触发RetryManager.java—— 重试机制RpcPushService.java—— gRPC 推送实现
2493

被折叠的 条评论
为什么被折叠?



