Nacos 客户端是如何感知配置变更的?
Nacos 客户端感知配置变更的核心机制可以概括为:“客户端长轮询(Long Polling)”(针对 Nacos 1.x HTTP 协议)和 “gRPC 双向流”(针对 Nacos 2.x)。
其中,长轮询是 Nacos 最经典的设计,它结合了 Push(推送)和 Pull(拉取)的优点。
以下是详细的原理分析:
1. 核心机制:长轮询 (Long Polling)
在 Nacos 1.x 版本(以及 2.x 兼容模式)中,客户端并不是简单地定时“拉”数据,也不是服务器主动建立长连接“推”数据,而是采用了长轮询。
流程步骤:
客户端发起请求:
客户端(ClientWorker)发起一个 HTTP 请求给服务端,询问:“配置有没有变化?”。这个请求的超时时间设置得很长(默认 30 秒)。服务端“挂起”请求:
服务端收到请求后,不会立即返回结果。- 它会检查客户端传来的配置 MD5 与服务端的 MD5 是否一致。
- 如果有变化:立即返回最新的配置信息。
- 如果没有变化:服务端利用 Servlet 3.0 的异步机制(
AsyncContext),将这个 HTTP 请求“挂起”(Hold 住),暂时不响应,放入一个队列中。
等待变更或超时:
服务端会保持这个连接打开,直到以下任一情况发生:- 情况 A:配置发生变更(用户在控制台修改了配置)。服务端触发事件,找到挂起的请求,立即将变更的配置 ID 返回给客户端。
- 情况 B:超时(约 29.5秒)。如果在等待期间一直没有配置变更,为了防止连接超时断开,服务端会在 29.5 秒左右(略小于客户端设置的 30 秒)自动返回一个“无变更(304)”的响应。
客户端处理响应:
- 如果收到配置变更的响应,客户端会立即发起一次普通的 GET 请求,拉取具体的配置内容,更新本地缓存,并触发用户注册的监听器(Listener)。
- 如果收到无变更响应,客户端会立即再次发起下一个长轮询请求,周而复始。
优点:这种方式既保证了配置变更的实时性(类似 Push),又避免了服务端维护大量长连接的资源消耗(类似 Pull 的无状态特性),同时减少了无效的网络交互。
2. Nacos 2.x 的升级:gRPC 长连接
从 Nacos 2.0 开始,为了提升性能和连接数支持,底层协议从 HTTP 升级为了 gRPC。
- 机制:客户端与服务端建立持久化的 gRPC 长连接。
- 感知方式:
- 客户端注册监听器到服务端。
- 当服务端配置发生变更时,服务端通过 gRPC 的双向流(Bi-Directional Stream)主动将变更推送到客户端。
- 这种方式比 HTTP 长轮询更加高效,延迟更低,且减少了频繁建立 HTTP 连接的开销。
3. 客户端内部工作流程 (ClientWorker)
无论是 HTTP 还是 gRPC,Nacos 客户端内部都有一个核心组件 ClientWorker 来管理这一过程。以下是更细致的内部逻辑:
- 分片检查:如果客户端监听了大量的配置(例如几千个),Nacos 会将这些配置分成多个任务块(CacheData),每个任务块最多包含 3000 个配置 ID。
- 线程池调度:
ClientWorker维护一个线程池,定期执行长轮询任务。 - MD5 比对:
- 客户端请求时,会将本地缓存的配置指纹(MD5)带给服务端。
- 服务端比对 MD5,只有 MD5 不一致时才认为有变更。
- 双重保险(容灾):
- 内存缓存:客户端内存中有一份配置缓存。
- 本地文件快照:Nacos 客户端会将拉取到的配置在本地磁盘(通常是
~/nacos/config目录下)保存一份快照。 - 作用:当 Nacos 服务端彻底宕机时,客户端可以降级读取本地文件,保证应用能启动或运行。
总结
Nacos 客户端感知配置变更的过程可以概括为:
- 客户端启动一个线程,不断向服务端发起长轮询请求(或建立 gRPC 流)。
- 服务端利用异步机制Hold 住请求,直到配置更新或超时。
- 一旦配置变更,服务端立即返回变更的 DataID。
- 客户端收到变更通知后,拉取最新配置,更新本地缓存和文件,并回调用户的 Listener。
右滑查看面试常问