Nacos 消费者(Client)如何获取最新的服务列表?
Nacos 消费者(Client)获取最新服务列表的机制采用了 “推拉结合”(Push + Pull) 的策略。这种混合模式既保证了服务列表更新的实时性,又保证了数据的最终一致性和系统的健壮性。
具体来说,主要包含以下三个核心机制:
1. 定时拉取(Pull - 兜底机制)
这是客户端主动发起的行为,用于确保本地缓存与服务器端数据的最终一致性。
- 机制: Nacos Client 内部有一个核心类
HostReactor,它会启动一个定时任务(UpdateTask)。 - 频率: 默认情况下,每隔 10秒(可以通过配置调整),客户端会向 Nacos Server 发送 HTTP 请求(或 gRPC 请求),拉取订阅的服务列表。
- 作用: 防止因网络抖动、推送失败等原因导致客户端长时间无法感知服务变化。这是一个“兜底”方案。
2. 实时感知(Push/Long Polling - 核心机制)
这是为了解决定时拉取延迟过高的问题(如果只靠10秒一次的拉取,服务下线后客户端感知会有延迟)。Nacos 1.x 和 2.x 在这方面的实现有较大区别:
Nacos 1.x:HTTP 长轮询 (Long Polling)
- 原理: 客户端发起一个 HTTP 请求询问服务端“服务列表有没有变化?”
- 服务端处理:
- 如果有变化:服务端立即返回最新的服务列表。
- 如果无变化:服务端不会立即返回,而是将该请求“挂起”(Hold 住),默认挂起约 30秒。
- 如果在挂起期间发生了服务变更(如新实例注册),服务端会立即唤醒该请求并返回数据。
- 如果挂起超时(30秒内无变化),服务端返回 304(未修改),客户端收到后再次发起长轮询。
- 优点: 模拟了“推送”的效果,实时性很高(毫秒级),同时避免了建立大量长连接的开销(在 HTTP/1.1 时代)。
Nacos 2.x:gRPC 长连接 (真正的 Push)
- 原理: Nacos 2.x 引入了 gRPC,客户端与服务端建立持久化的双向流式连接。
- 服务端处理: 当服务端感知到服务列表发生变更时,直接通过建立好的 gRPC 通道,主动将变更事件(
NotifySubscriberRequest)推送给客户端。 - 优点: 相比 HTTP 长轮询,减少了 HTTP 请求头的开销,连接更稳定,实时性更强。
3. 本地缓存与容灾 (Local Cache)
客户端获取到服务列表后,不仅仅是放在内存里,还会进行持久化,以应对 Nacos Server 挂掉的极端情况。
- 内存缓存: 客户端将服务列表保存在内存中的
ServiceInfoMap中,业务发起调用时,直接从内存读取,性能极高。 - 本地文件缓存(故障转移):
- 客户端会将服务列表快照写入本地磁盘(通常在
~/nacos/cache/目录下)。 - 容灾场景: 当 Nacos Server 集群全部宕机,或者网络完全中断时,客户端会降级读取本地磁盘的缓存文件,确保服务调用不会立即瘫痪(虽然列表可能不是最新的,但比无法调用要好)。
- 客户端会将服务列表快照写入本地磁盘(通常在
总结:完整流程
- 启动时: Client 启动,主动向 Server 发起一次请求,拉取全量服务列表,填充本地内存和文件缓存。
- 运行时(实时更新):
- 1.x: 开启 HTTP 长轮询,Server 有变动立即返回,无变动则等待。
- 2.x: 建立 gRPC 连接,Server 有变动直接推送。
- 运行时(兜底): 每 10 秒执行一次定时拉取,校对数据,防止推送丢失。
- 故障时: 若连接 Server 失败,自动降级读取本地磁盘缓存。
这种设计使得 Nacos 既拥有 Zookeeper 般的实时性(通过推送/长轮询),又拥有 Eureka 般的可用性(通过本地缓存和定时拉取)。