孤舟笔记 Spring全家桶篇二十六 简述Nacos配置更新的工作流程?配置变更是怎么通知到服务的

文章目录

个人网站

配置中心是微服务架构的基础设施,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 协议保证配置数据的强一致性。

加分回答

  1. 长轮询 vs 长连接:Nacos 1.x 使用长轮询,Nacos 2.x 引入了 gRPC 长连接,配置变更可以通过长连接实时推送,延迟更低。但长轮询作为降级方案仍然保留
  2. @RefreshScope 的坑:@RefreshScope 会导致 Bean 被代理,代理对象在首次访问时才创建。如果刷新后 Bean 创建失败,会导致服务不可用------生产环境慎用
  3. 灰度发布:Nacos 支持 Beta 配置发布,可以指定 IP 让部分实例先收到新配置,验证没问题再全量发布,降低配置变更的风险

面试官点评

这道题考的是你对 配置中心原理 的理解。能说出长轮询机制算及格,高分的关键在于:解释为什么选长轮询而不是推送 (实时性和复杂度的权衡),以及 @RefreshScope 的工作原理(代理 + 重建)。如果能提到 Nacos 2.x 的 gRPC 改进,说明你关注技术演进。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

相关推荐
落魄江湖行16 小时前
孤舟笔记 互联网常用框架篇二 Dubbo服务请求失败怎么处理?集群容错策略你用过几种
春招·孤舟笔记
落魄江湖行19 小时前
孤舟笔记 Spring全家桶篇二十五 谈谈EurekaServer数据同步原理?注册中心怎么保证数据一致性
春招·孤舟笔记·谈谈eurekaserver数
落魄江湖行19 小时前
孤舟笔记 互联网常用框架篇三 Dubbo是如何动态感知服务下线的?注册中心和服务端双保险
春招·孤舟笔记·dubbo是如何动态感知服务下
落魄江湖行17 天前
孤舟笔记 IO 与网络编程篇三 IO和NIO的区别是什么?从阻塞到非阻塞的范式革命
春招·孤舟笔记·io和nio的区别
落魄江湖行22 天前
孤舟笔记 并发篇十八 为什么启动线程不能直接调用run()方法?调用两次start()又会怎样?这个设计藏着大智慧
thread·java并发·春招·孤舟笔记
逻辑驱动的ken22 天前
Java高频面试考点场景题22
java·开发语言·jvm·面试·职场和发展·求职招聘·春招
落魄江湖行23 天前
孤舟笔记 并发篇二十九 volatile关键字有什么用?它的实现原理是什么?面试必问的轻量级同步机制
java并发·春招·孤舟笔记·volatile关键字
落魄江湖行23 天前
孤舟笔记 并发篇二十八 wait和sleep是否会触发锁的释放及CPU资源的释放?这个区别面试必考
java并发·春招·孤舟笔记·wait和sleep
落魄江湖行23 天前
孤舟笔记 并发篇二十二 线程池是如何回收线程的?核心线程和非核心线程的回收逻辑大不相同
java并发·春招·孤舟笔记·线程池是如何回收线程的