Nacos Push机制 源码详解

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 长连接推送

建议阅读源码路径

  1. PushService.java —— 推送核心
  2. PushTask.java —— 推送任务
  3. ConfigServletInner.java —— 发布与推送触发
  4. RetryManager.java —— 重试机制
  5. RpcPushService.java —— gRPC 推送实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值