Skip to content

Nacos ​

Java 高级 · 阿里中间件

Nacos 在微服务里一肩挑"注册中心"与"配置中心"。但很多人只用过 @EnableDiscoveryClient 和 @Value,却回答不了:实例宕机后多久被剔除?配置变更为什么秒级推送?Nacos 挂了应用还能启动吗? 本文讲清心跳保活、Distro/Raft 双一致性、长轮询监听三大机制,以及它们背后的取舍。

一、数据模型:三层隔离 ​

Nacos 用 Namespace → Group → DataId/ServiceName 三级定位一份配置或一个服务:

  • Namespace:环境/租户隔离(dev/test/prod),不同 namespace 完全不可见。
  • Group:同一环境内的业务分组(如 ORDER_GROUP、USER_GROUP)。
  • DataId / ServiceName:具体配置项或服务名。

三者组合唯一定位资源。这是多环境、多租户管控的基础——Namespace 配错就会把 prod 配置拉到 dev 应用。

二、服务注册与发现:心跳 + 健康检查 ​

2.1 注册与心跳保活 ​

服务实例启动时向 Nacos Server 注册(POST /nacos/v1/ns/instance),之后客户端每 5 秒发一次心跳(Beat)续租。服务端维护每个实例的 lastBeat 时间戳:

  • 临时实例(默认,AP):靠心跳保活,超时(默认 30s 无心跳)直接剔除。宕机即消失,可用性优先。
  • 持久实例(CP):由服务端主动健康检查(TCP/HTTP 探活)维护,即使进程挂了也不会被自动剔除,需手动下线。强一致优先。

2.2 服务发现与推送 ​

Consumer 启动时订阅服务,拉全量实例列表 + 长连接监听增量。实例上下线时,Server 通过 gRPC 长连接(2.x)或 UDP(1.x) 主动推送变更,Consumer 本地维护一份实例缓存,配合负载均衡(如 Ribbon / Spring Cloud LoadBalancer)选节点。

三、一致性协议:Distro(AP) + Raft(CP) 双轨 ​

Nacos 2.x 的核心设计是按数据特性选择一致性协议,而不是一刀切:

Distro(AP,自研):

  • 每个 Server 节点是对等的,但每个数据项有且仅有一个"负责节点"(按 hash(key) % 节点数 划分)。
  • 写请求路由到负责节点,该节点同步更新本地后,异步复制给其他节点(保证最终一致)。
  • 节点扩缩容时做数据分片再平衡(distro 数据校验与同步)。临时实例和配置用 Distro——因为它们对可用性要求高,短暂不一致可接受。

Raft(CP,基于 Apache Ratis):

  • 用于持久实例、集群元数据、配置的历史版本等需要强一致的数据。
  • 集群选出一个 Leader,所有写先到 Leader,日志复制到**多数派(quorum)**才提交。Leader 挂了触发重新选举。
  • 因为需要多数派存活,Raft 集群至少 3 节点,且能容忍 (N-1)/2 个节点故障。

TIP

为什么这么拆?注册中心最怕"因为一致性协议太重而不可用"——服务发现偶尔读到旧实例列表,比整个注册中心挂掉要好得多,所以临时实例走轻量的 Distro(AP)。但集群的"谁是当前 Leader、哪些节点在线"这种元数据必须强一致,否则会出现脑裂,所以走 Raft(CP)。一个 Nacos 集群同时跑两种协议,各管各的数据。这就是阿里"AP+CP 混合"设计的精髓。

四、配置中心:长轮询监听 ​

配置变更如何做到"秒级推送且不丢"?Nacos 1.x 用 长轮询(Long Polling):客户端发起一个最长 30s 的 HTTP 请求,Server 端 hold 住;若 30s 内配置无变更,超时返回空;若有变更,立即返回变更的 DataId 列表。客户端据此再去拉最新配置。

Nacos 2.x 把配置监听升级为 gRPC 长连接,变更由服务端主动推,延迟更低、资源占用更少。客户端还会把配置落本地快照(localSnapshot),Nacos 不可用时仍能以快照启动——这是"配置中心挂了应用还能起"的关键。

4.1 动态刷新代码示例 ​

java
@RefreshScope                // 配置变更时销毁并重建该 Bean, 重新注入最新值
@RestController
public class ConfigCtrl {
    @Value("${order.threshold:100}")
    private int threshold;    // Nacos 改了 order.threshold, 下次注入即生效

    @NacosValue(value = "${order.threshold:100}", autoRefreshed = true)
    public void setThreshold(int t) { this.threshold = t; }
}

五、生产落地要点 ​

  • 至少 3 节点集群:Raft 要多数派,单点无高可用;Distro 也需多点分摊压力。
  • 环境严格隔离:Namespace 按环境分,发布前校验应用连的 namespace 是否对。
  • 配置拆分:别把全部配置塞进一个 DataId,按业务域 + 稳定性拆分,热点配置独立避免变更波及面过大。
  • 失败降级:开本地快照;核心配置结合 KMS 不存明文密钥。
  • 灰度发布 + 回滚:Nacos 支持配置灰度与一键回滚,变更先小流量验证。

WARNING

  • 临时实例 vs 持久实例混淆:核心服务建议持久实例 + 健康检查,避免"进程假死但心跳还在"被持续路由;非核心无状态服务用临时实例更轻。
  • 把敏感信息明文存配置中心:密钥应结合 KMS 或只在启动注入。
  • Namespace 用错导致环境串味:dev 连 prod 会拉到生产配置,是线上事故的常见源头。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 设计一个"多环境(dev/test/prod) + 多业务组(订单/用户)"的 Namespace/Group/DataId 划分方案,并说明隔离目标。
  2. 用 @RefreshScope 验证修改 Nacos 配置后 Bean 字段实时生效,画出"长轮询 → 拉取 → 重建 Bean"的链路。
  3. 比较 Nacos 的 Distro 与 Raft 分别保障哪类数据,为什么临时实例用 AP、集群元数据用 CP。
  4. 若 Nacos 集群整体宕机,已运行的 Consumer/Provider 会怎样?为什么?结合本地快照与本地实例缓存解释。

本节小结

Nacos 一肩挑"注册 + 配置":靠 Namespace/Group/DataId 三层隔离做多环境管控;服务发现靠心跳保活(AP 临时实例 / CP 持久实例);配置靠长轮询/gRPC 长连接监听 + 本地快照降级。最精妙的是一致性协议按数据特性双轨——Distro(AP) 管临时实例与配置(高可用),Raft(CP) 管持久实例与元数据(强一致)。落地铁律:环境隔离、集群化、灰度发布、快照兜底。