从1秒延迟到20毫秒:Nacos 2.x 为什么敢把 HTTP 全换 gRPC


升级之后,服务发现的延迟降了96%

去年把公司 Nacos 从 1.4 升级到 2.1,上线后监控面板上服务发现延迟这条线从 800ms 直接砸到 20ms。

运维老张第一反应是监控出 bug 了。

"没 bug。"我把压测数据发给他。“1.x 用 HTTP 短轮询,客户端每 5 秒发一次请求。大部分请求返回’没有变更’。2.x 换 gRPC 长连接,服务端有变更直接 Push,不需要客户端来问。”

老张沉默了一会:“所以之前我们 50 个微服务,每 5 秒一轮 HTTP 请求,一天就是 80 万次请求,其中 78 万次是白费 CPU。”

算了一下,对。

这篇文章把 Nacos 从 HTTP 到 gRPC 的技术决策拆开来讲——为什么要换、换了之后强在哪、新旧客户端怎么兼容。


1.x 时代:HTTP 短轮询的三层困境

第一层:无效请求占比 90% 以上

1.x 的服务发现流程:

Nacos Server 客户端 Nacos Server 客户端 loop [每5秒一轮] 大部分请求返回"没有变更",白白消耗带宽和CPU GET /nacos/v1/ns/instance/list?serviceName=order-service 实例列表(或"没有变更")

客户端每 5 秒打一次 HTTP 请求,哪怕服务列表没有任何变化。90% 以上的响应是"没有变更"。

50 个微服务 × 每 5 秒一次 × 一天的请求量 = 864,000 次。其中真正有变更的不到 10%。

第二层:UDP 推送可靠性差

Nacos 1.x 也想做 Push。它用 UDP 协议向客户端推送变更通知。但 UDP 不保证送达。

如果网络抖动丢了包,客户端根本不知道有变更发生。它只能等下一个 5 秒的轮询周期才能拿到新数据。

这就导致一个场景:服务提供者下线了,Nacos 检测到了,发了 UDP 推送,丢了。消费者在长达 5 秒的时间里还在往已经死掉的地址发请求。

UDP 推送 = 可能推送。

第三层:连接不能复用

// Nacos 1.x 客户端内部
// ConfigService 和 NamingService 各自维护 HTTP 连接池

// 每新增一个 Listener,就多一个长轮询连接
configService.addListener("database.yaml", "ORDER_GROUP", new Listener() {
    // 这个监听器占一条 HTTP 连接
});

NamingService 的订阅、ConfigService 的监听,每个都独立建 HTTP 连接。一个微服务进程跑起来,Nacos 客户端动不动就开了 6~8 条连接。

扩展到 50 个微服务 × 8 条连接 = Nacos Server 上 400 条常驻连接。


2.x 翻盘:gRPC 的四张王牌

gRPC 的四个优势

双向流
Bi-directional Streaming

单条长连接
Connection Multiplexing

服务端主动 Push
Server Push

多请求复用
Request Multiplexing

客户端和服务端
同时发送消息

一条 TCP 连接承载
所有请求类型

延迟从秒级
降到毫秒级

不需要队头阻塞
等待前面的请求

gRPC 的四张王牌:双向流、长连接复用、服务端 Push、请求多路复用。


王牌一:双向流——客户端和服务端能同时说话

HTTP 模式是"请求-响应"。客户端问一句,服务端答一句。一轮结束,下一轮需要重新发起。

gRPC 的 Bi-directional Streaming 模式是:客户端和服务端之间的通道随时可以往里面塞消息。

Nacos Server gRPC 2.x 客户端 Nacos Server gRPC 2.x 客户端 30秒后,有实例下线 5分钟后,配置被修改 建立 gRPC 双向流连接 注册请求(registerInstance) 订阅请求(subscribe) 配置监听(addListener) 注册成功 实例列表 配置内容 Push: 服务列表变更 ACK Push: 配置变更通知 拉取新配置 新配置内容

一条连接上同时跑注册、订阅、配置监听、心跳四种流量。服务端有变更直接 Push,不需要等客户端来问。

王牌二:单条连接承载所有请求类型

Nacos 1.x:

[连接1] → 服务注册
[连接2] → 服务订阅
[连接3] → 心跳
[连接4] → 配置拉取
[连接5] → 配置监听
[连接6] → 配置监听2

Nacos 2.x:

[一条 gRPC 连接] → 所有请求类型复用

50 个微服务 × 1 条连接 = Nacos Server 上 50 条连接

连接数从 400 降到 50。服务端的线程模型也跟着简化。

王牌三:服务端主动 Push,延迟降 96%

这是 gRPC 最直接的价值。看两组数据:

场景 HTTP 短轮询(1.x) gRPC 长连接(2.x)
服务上线通知 最坏 5 秒(下一个轮询周期) 平均 20ms
服务下线通知 最坏 5 秒 + UDP 可能丢包 平均 20ms
配置变更推送 最多 30 秒(长轮询 Hold 上限) 平均 15ms

延迟从秒级降到毫秒级,不是优化,是换代。

王牌四:请求多路复用——不需要排队

HTTP/1.1 有队头阻塞:前一个请求的响应慢了,后面的请求都得等着。

gRPC 基于 HTTP/2,在一条 TCP 连接上多路复用多个 Stream。每个 Stream 独立,互不阻塞。

gRPC HTTP/2

Stream1

响应1

Stream2

响应2
不等

Stream3

响应3
不等

HTTP/1.1

请求1

响应1

请求2

等响应1结束

请求3

等响应2结束

HTTP/1.1 队头阻塞 vs gRPC HTTP/2 多路复用。后者每个 Stream 独立,互不影响。


性能对比:数据不会说谎

指标 Nacos 1.x HTTP Nacos 2.x gRPC 提升
注册 TPS ~800 ~3000 3.75x
服务发现延迟 800~5000ms 10~50ms 40~500x
配置推送延迟 1000~30000ms 10~30ms 100~3000x
单节点连接数 400(50服务) 50(50服务) 8x 减少
无效请求占比 90%+ 0%(Push 模式) 清零

最夸张的一行是配置推送延迟。1.x 长轮询 Hold 住连接最长 30 秒,运气好秒级,运气不好半分钟。2.x 之后 gRPC 直接 Push,几乎感觉不到延迟。


兼容性设计:1.x 和 2.x 客户端怎么共存

Nacos 没有一刀切。2.x 服务端同时暴露两个端口:

端口 8848 → HTTP 通道(给 1.x 客户端和 OpenAPI)
端口 9848 → gRPC 通道(给 2.x 客户端 SDK)

同一个注册请求,两种走法:

2.x nacos-client

1.x nacos-client

非 Java OpenAPI

注册请求

客户端版本?

gRPC Bidirectional Stream
端口 9848

HTTP POST
/nacos/v1/ns/instance
端口 8848

最终都到 InstanceController

两条通道最终到达同一个 Controller。不同的是:gRPC 通道走双向流 Push 变更,HTTP 通道走短轮询。

升级策略

# 第一阶段:升级服务端到 2.x(不升级客户端)
# 1.x 客户端继续走 8848 HTTP 端口,一切正常

# 第二阶段:灰度升级客户端到 2.x
# 在 pom.xml 里改版本号
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>2.2.0</version>   <!-- 从 1.4.x 升上来 -->
</dependency>

# 不需要改代码。nacos-client 2.x 自动走 gRPC
# bootstrap.yml 里的 server-addr 写法不变

升级客户端不需要改业务代码,不需要改配置。nacos-client 2.x 内部自动从 HTTP 切到 gRPC。对业务服务完全透明。


一条完整的通信链路(2.x 视角)

Nacos gRPC(9848) nacos-client 2.x 业务代码 Nacos gRPC(9848) nacos-client 2.x 业务代码 loop [每5秒] 30秒后,某实例下线 namingService.registerInstance(...) 构建 GrpcClient 实例 建立 BidirectionalStream 连接 ServerCheckRequest (校验服务端版本,确认兼容) ServerCheckResponse (版本匹配) InstanceRequest (registerInstance, 序列化为 protobuf) InstanceResponse (注册成功) HealthCheckRequest (心跳,复用同一条连接) HealthCheckResponse NotifySubscriberRequest (服务端主动 Push 变更) AckRequest 刷新本地缓存,回调 NotifyListener

注册 → 心跳 → 变更推送,全程一条 gRPC 连接。protobuf 序列化比 JSON 更紧凑,传输体积更小。


总结

Nacos 从 HTTP 换到 gRPC,不是一个 API 替换,是一次通信模型的换代。

  1. HTTP 短轮询:客户端定时问"变了吗",90% 的请求白费。延迟取决于轮询间隔。
  2. gRPC 双向流:服务端有变更直接 Push,延迟从秒级降到毫秒级。
  3. 连接复用:一条 TCP 连接承载注册、订阅、配置监听、心跳四种流量。
  4. 多路复用:多个 Stream 独立并发,不存在队头阻塞。
  5. 兼容共存:8848 端口继续服务 1.x 客户端,9848 端口给 2.x 客户端。升级零感知。

如果你还在跑 Nacos 1.x,升级的理由已经够了——延迟降 96%,连接数降 8 倍,不需要改任何业务代码。


你们现在是 Nacos 1.x 还是 2.x?评论区留个数字:1=还在1.x观望 2=已升2.x 3=还没用过Nacos。顺带说一下升级最大的顾虑是什么。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐