Nacos 如何实现配置的动态更新(热加载)
Nacos 实现配置动态更新(热加载)的核心机制可以概括为:客户端长轮询(Long Polling) + 服务端事件通知 + 客户端双重检测。
在 Nacos 2.x 版本中,底层通信协议升级为 gRPC,但其逻辑本质依然是保持长连接以实时感知变化。以下主要以经典的 HTTP 长轮询机制(Nacos 1.x 核心,也是面试和理解原理的重点)为例进行详细解析,并在最后补充 Nacos 2.x 的变化。
核心流程图解
整个过程可以分为以下几个关键步骤:
- 客户端发起长轮询:客户端询问服务端配置是否变更。
- 服务端挂起请求:如果没有变更,服务端暂时不回复,而是“拿住”请求。
- 配置变更/超时:当配置发生变化或达到等待超时时间,服务端返回结果。
- 客户端拉取与回调:客户端收到变更通知,拉取最新配置,更新本地缓存,并触发监听器。
详细实现步骤
1. 客户端:发起长轮询 (Client Worker)
Nacos 客户端有一个专门的线程池(ClientWorker),负责维护所有的配置监听。
- 分批检查:如果监听的配置很多(例如 3000 个),Nacos 会将它们按每批 3000 个(默认)拆分,通过线程池并发处理。
- 发送 MD5:客户端并不是直接拉取配置内容,而是将自己本地缓存的配置的 MD5 值(指纹)发送给服务端。
- 请求内容:请求中包含
DataId、Group、Tenant以及本地的MD5。
2. 服务端:处理长轮询 (Long Polling)
服务端接收到请求后,不会立即返回,而是利用 Servlet 3.0 的异步特性(AsyncContext) 进行处理:
- 比对 MD5:服务端先检查客户端传来的 MD5 与服务端最新的 MD5 是否一致。
- 如果不一致:说明配置已更新,立即返回 变更的
DataId和Group。 - 如果一致:说明配置没变。服务端将该 HTTP 请求挂起(Suspend),放入一个队列中,不立即发送 HTTP 响应。
- 如果不一致:说明配置已更新,立即返回 变更的
- 等待时长:默认挂起时间为 29.5 秒(客户端超时设置为 30 秒,服务端预留 0.5 秒缓冲)。
3. 服务端:事件触发与调度
在请求挂起的这 29.5 秒内,会发生两种情况:
情况 A:配置发生了修改(热更新核心)
- 用户在 Nacos 控制台或通过 API 修改了配置。
- 服务端触发
LocalDataChangeEvent事件。 - 专门的监听机制(
LongPollingService)捕获到该事件。 - 服务端从挂起的队列中找到订阅了该配置的请求(AsyncContext)。
- 立即激活该请求,将变更的配置信息写入响应,返回给客户端。
情况 B:一直没有修改
- 29.5 秒时间到了。
- 调度任务将请求“唤醒”。
- 服务端返回一个空的响应(或 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 客户端采用了三级存储:
- 一级:内存缓存:优先读取内存中的配置。
- 二级:本地文件快照(Local Snapshot):每次从服务端拉取配置成功后,都会在本地磁盘(通常是
~/nacos/config目录)保存一份快照。如果服务端宕机,客户端会降级读取本地文件。 - 三级:远程服务端:最终的数据源。
3. Nacos 2.x 的变化 (gRPC)
在 Nacos 2.0 以后,为了提升性能(解决 HTTP 短连接频繁创建销毁的开销),引入了 gRPC。
- 长连接:客户端与服务端建立持久的 gRPC 连接。
- 流式推送:不再是 HTTP 的“请求-挂起-响应”模式,而是服务端注册了客户端的连接流。当配置变更时,服务端通过 gRPC Stream 主动推送 变更数据给客户端。
- 兼容性:逻辑层面依然保留了“检查 MD5 -> 通知变更 -> 拉取/推送”的核心思想,只是传输通道变得更高效了。
总结
Nacos 的热加载不是魔法,而是通过 “客户端死循环请求 + 服务端异步挂起 + 事件监听机制” 来实现的。这种设计保证了配置变更的实时性(毫秒级延迟)和系统的低开销。
右滑查看面试常问