基于本文回答
0
评论

Nacos 如何实现配置的动态更新(热加载)

知识点图片

Nacos 实现配置动态更新(热加载)的核心机制可以概括为:客户端长轮询(Long Polling) + 服务端事件通知 + 客户端双重检测

在 Nacos 2.x 版本中,底层通信协议升级为 gRPC,但其逻辑本质依然是保持长连接以实时感知变化。以下主要以经典的 HTTP 长轮询机制(Nacos 1.x 核心,也是面试和理解原理的重点)为例进行详细解析,并在最后补充 Nacos 2.x 的变化。


核心流程图解

整个过程可以分为以下几个关键步骤:

  1. 客户端发起长轮询:客户端询问服务端配置是否变更。
  2. 服务端挂起请求:如果没有变更,服务端暂时不回复,而是“拿住”请求。
  3. 配置变更/超时:当配置发生变化或达到等待超时时间,服务端返回结果。
  4. 客户端拉取与回调:客户端收到变更通知,拉取最新配置,更新本地缓存,并触发监听器。

详细实现步骤

1. 客户端:发起长轮询 (Client Worker)

Nacos 客户端有一个专门的线程池(ClientWorker),负责维护所有的配置监听。

  • 分批检查:如果监听的配置很多(例如 3000 个),Nacos 会将它们按每批 3000 个(默认)拆分,通过线程池并发处理。
  • 发送 MD5:客户端并不是直接拉取配置内容,而是将自己本地缓存的配置的 MD5 值(指纹)发送给服务端。
  • 请求内容:请求中包含 DataIdGroupTenant 以及本地的 MD5

2. 服务端:处理长轮询 (Long Polling)

服务端接收到请求后,不会立即返回,而是利用 Servlet 3.0 的异步特性(AsyncContext) 进行处理:

  • 比对 MD5:服务端先检查客户端传来的 MD5 与服务端最新的 MD5 是否一致。
    • 如果不一致:说明配置已更新,立即返回 变更的 DataIdGroup
    • 如果一致:说明配置没变。服务端将该 HTTP 请求挂起(Suspend),放入一个队列中,不立即发送 HTTP 响应。
  • 等待时长:默认挂起时间为 29.5 秒(客户端超时设置为 30 秒,服务端预留 0.5 秒缓冲)。

3. 服务端:事件触发与调度

在请求挂起的这 29.5 秒内,会发生两种情况:

  • 情况 A:配置发生了修改(热更新核心)

    1. 用户在 Nacos 控制台或通过 API 修改了配置。
    2. 服务端触发 LocalDataChangeEvent 事件。
    3. 专门的监听机制(LongPollingService)捕获到该事件。
    4. 服务端从挂起的队列中找到订阅了该配置的请求(AsyncContext)。
    5. 立即激活该请求,将变更的配置信息写入响应,返回给客户端。
  • 情况 B:一直没有修改

    1. 29.5 秒时间到了。
    2. 调度任务将请求“唤醒”。
    3. 服务端返回一个空的响应(或 304 Not Modified),告知客户端“无变更”。

4. 客户端:处理响应与更新

客户端收到服务端的响应后:

  • 如果有变更:服务端只返回了“哪些配置变了”(DataId/Group),客户端会立即发起第二次请求,精准拉取这些变更配置的具体内容(Content),然后更新本地内存(Snapshot),并计算新的 MD5。
  • 触发回调:更新本地缓存后,Nacos 客户端会遍历用户注册的 Listener,调用 receiveConfigInfo 方法。
  • Spring 集成:如果是 Spring Cloud 项目,此时会触发 ContextRefresher,通过 Spring 的事件机制刷新 Environment,并重新生成标注了 @RefreshScope 的 Bean,从而实现代码层面的“热加载”。
  • 循环:无论是否有变更,请求结束后,客户端会立即发起下一次长轮询,周而复始。

关键技术点总结

1. 为什么用长轮询(Long Polling)而不是 Push 或 Pull?

  • 对比 Pull(短轮询):短轮询需要客户端频繁发送请求(如每秒一次),如果配置不常变,会浪费大量带宽和服务器资源。
  • 对比 Push(服务端推送):纯 Push 需要服务端维护海量的长连接(在 HTTP 1.1 时代较重),且服务端需要知道客户端的状态,容易导致状态不一致。
  • 长轮询优势:结合了 Push 的实时性(有变更立即返回)和 Pull 的轻量级(由客户端发起,服务端无状态)。它给用户的感觉是“服务端推送了变更”,但本质是“客户端一直在等”。

2. 容灾机制(Failover)

为了保证高可用,Nacos 客户端采用了三级存储

  1. 一级:内存缓存:优先读取内存中的配置。
  2. 二级:本地文件快照(Local Snapshot):每次从服务端拉取配置成功后,都会在本地磁盘(通常是 ~/nacos/config 目录)保存一份快照。如果服务端宕机,客户端会降级读取本地文件。
  3. 三级:远程服务端:最终的数据源。

3. Nacos 2.x 的变化 (gRPC)

在 Nacos 2.0 以后,为了提升性能(解决 HTTP 短连接频繁创建销毁的开销),引入了 gRPC

  • 长连接:客户端与服务端建立持久的 gRPC 连接。
  • 流式推送:不再是 HTTP 的“请求-挂起-响应”模式,而是服务端注册了客户端的连接流。当配置变更时,服务端通过 gRPC Stream 主动推送 变更数据给客户端。
  • 兼容性:逻辑层面依然保留了“检查 MD5 -> 通知变更 -> 拉取/推送”的核心思想,只是传输通道变得更高效了。

总结

Nacos 的热加载不是魔法,而是通过 “客户端死循环请求 + 服务端异步挂起 + 事件监听机制” 来实现的。这种设计保证了配置变更的实时性(毫秒级延迟)和系统的低开销

右滑查看面试常问