Nacos 注册发现与配置中心
Nacos 是 Spring Cloud Alibaba 体系里常用的注册中心和配置中心。它同时解决两个核心问题:
- 服务怎么找到彼此:服务实例会扩容、缩容、重启、迁移,调用方不能写死 IP。
- 配置怎么集中管理:不同环境、不同服务、不同租户的配置需要统一发布、审计、回滚和动态生效。
零基础可以先记住一句话:
Nacos Discovery 负责“服务名到实例列表”,Nacos Config 负责“配置文件到应用 Environment”。
阅读入口:先建立两条主线
按“注册发现 → 配置加载 → 运行期变更 → 故障排查”阅读。先看原理图,再看基础配置和完整流程,最后进入内部原理与生产治理。
核心原理:注册发现与配置分成两条数据流
Discovery 维护实例元数据并通过心跳、订阅和本地缓存传播;Config 按 dataId/group/namespace 管理配置版本,通过长轮询或推送通知客户端更新。两条数据流可以使用同一个 Nacos 集群,但存储、权限和故障影响不同。
flowchart TD
A["服务启动"] --> B["Discovery注册实例"]
B --> C["心跳/健康检查"]
D["应用启动"] --> E["Config加载dataId"]
E --> F["PropertySource进入Environment"]
F --> G["监听变更并刷新"]
C --> H["消费者订阅实例快照"]本页负责零基础概念、常用配置和基础排查。Nacos 1.x/2.x通信差异、gRPC连接、注册重做、服务订阅推送、ServiceInfoHolder、Distro、JRaft、Config MD5监听、快照降级、端口规划和生产故障恢复请继续阅读:
学习目标
学完本页你要能回答:
- Nacos 的注册中心和配置中心分别解决什么问题。
- Namespace、Group、Service、Cluster、Instance、DataId 分别是什么。
- 服务注册、心跳、健康检查、订阅推送、本地缓存的完整过程。
- 临时实例和持久实例有什么区别,为什么它们对应不同健康检查方式。
- Nacos 为什么既要本地缓存,又要服务端推送。
- Nacos Config 启动拉取和运行期刷新怎么工作。
- 配置动态刷新适合什么,不适合什么。
- Nacos 和 Eureka、Spring Cloud Config、Apollo 怎么比较。
- 服务找不到、配置不生效、改配置不刷新怎么排查。
- 商业项目里配置发布、权限、审计、回滚、灰度应该怎么做。
Nacos 的核心模型
flowchart TD
A["Nacos"] --> B["Naming<br/>注册发现"]
B --> C["Service、Instance、Cluster"]
C --> D["Config<br/>配置中心"]
D --> E["Namespace、Group、DataId"]图中为了在手机和窄屏上保持清晰,按“注册发现模型 -> 配置模型”纵向展示;Naming 与 Config 实际上是 Nacos 提供的两组并列能力,不是先后调用关系。各字段的准确职责以紧随其后的表格为准。
| 概念 | 属于 | 作用 | 示例 |
|---|---|---|---|
| Namespace | 注册和配置都可用 | 环境或租户隔离 | dev、test、prod |
| Group | 注册和配置都可用 | 分组隔离 | DEFAULT_GROUP、MEDICAL_GROUP |
| Service | 注册发现 | 服务名 | asset-service |
| Cluster | 注册发现 | 实例分组、机房、区域 | shanghai、beijing |
| Instance | 注册发现 | 一个运行中的服务实例 | 10.1.2.11:8080 |
| DataId | 配置中心 | 一份配置的标识 | asset-service-prod.yml |
常见误区:
| 误区 | 正确理解 |
|---|---|
| Namespace 只是页面分组 | Namespace 会影响服务发现和配置查找,不同 Namespace 默认互相看不见 |
| Group 可以随便写 | 服务提供者和消费者 Group 不一致,可能互相发现不到 |
| DataId 就是文件名 | DataId 是 Nacos 配置定位的一部分,和应用名、环境、后缀强相关 |
| Nacos 转发业务请求 | Nacos 只提供注册表和配置,不转发业务 HTTP 请求 |
为什么需要注册发现
没有注册中心时,调用方要写死服务地址:
http://10.1.2.11:8080/api/assets这在微服务里会带来问题:
- 服务扩容后,调用方不知道新实例。
- 服务宕机后,调用方还会打到坏实例。
- 容器重启后 IP 变化,配置要改。
- 多环境、多机房、多版本路由难维护。
- 负载均衡、灰度、权重都需要额外实现。
有 Nacos 后,调用方只关心服务名:
lb://asset-service底层流程是:
flowchart TD
A["asset-service 实例启动"] --> B["注册服务名、IP、端口、元数据"]
B --> C["Nacos 保存实例列表"]
C --> D["调用方订阅 asset-service"]
D --> E["实例列表进入本地缓存"]
E --> F["LoadBalancer 选择实例"]
F --> G["HTTP 调用真实地址"]前三步发生在提供者注册阶段,后四步发生在消费者订阅和业务调用阶段;纵向连接用于表达二者的数据依赖,不表示它们必须由同一线程连续执行。
服务注册全过程
服务实例启动后,会把自己注册到 Nacos。
flowchart TD
A["应用启动"] --> B["读取 spring.application.name"]
B --> C["读取 Nacos server-addr、namespace、group"]
C --> D["确定本机 IP、端口、metadata"]
D --> E["向 Nacos 注册 Instance"]
E --> F["Nacos 保存到服务实例列表"]
F --> G["客户端定时心跳或服务端健康检查"]注册信息通常包括:
| 字段 | 示例 | 作用 |
|---|---|---|
| 服务名 | asset-service | 调用方按服务名发现实例 |
| IP | 10.1.2.11 | 实际调用地址 |
| 端口 | 8080 | 实际调用端口 |
| Namespace | prod | 环境隔离 |
| Group | DEFAULT_GROUP | 服务分组 |
| Cluster | shanghai | 区域、机房、集群 |
| Weight | 1.0 | 权重路由 |
| Healthy | true | 健康状态 |
| Metadata | version=v1 | 灰度、标签、租户等 |
服务提供者配置示例:
spring:
application:
name: asset-service
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
namespace: prod
group: DEFAULT_GROUP
cluster-name: shanghai
metadata:
version: v1
zone: shanghai临时实例和持久实例
Nacos 实例有两类:临时实例和持久实例。旧资料也常把持久实例称为“永久实例”,本文统一使用更准确的“持久实例”。
| 类型 | 健康判断 | 适合场景 | 实例异常后 |
|---|---|---|---|
| 临时实例 | 客户端主动心跳 | 普通 Spring Cloud 微服务 | 心跳超时后自动删除 |
| 持久实例 | 服务端主动探测 | 固定 IP 服务、部分基础设施 | 不会因连接断开直接删除,通常标记不健康 |
临时实例更像“我的生命周期跟客户端会话或连接相关;连接失效后注册信息应被清理”。持久实例更像“这台机器长期存在,服务端可以主动探测它是否健康,但不要因为连接断开就直接删除注册数据”。Nacos 1.x常见心跳模型和Nacos 2.x连接模型的准确区别见深度原理页。
flowchart TD
A["临时实例"] --> B["依赖客户端会话或连接"]
B --> C["失联后清理注册"]
C --> D["持久实例"]
D --> E["服务端主动健康探测"]
E --> F["失败时标记不健康并保留数据"]Spring Cloud 微服务通常使用临时实例。因为容器实例频繁扩缩容,实例消失后应自动从注册表移除。
服务发现和订阅推送
消费者需要拿到实例列表。它不会每次调用都实时访问 Nacos,而是会维护本地缓存。
flowchart TD
A["消费者启动"] --> B["向 Nacos 订阅服务"]
B --> C["拉取当前实例列表"]
C --> D["写入本地缓存"]
D --> E["服务实例发生变化"]
E --> F["Nacos 通知订阅客户端"]
F --> G["消费者更新本地缓存"]
G --> H["业务请求到达"]
H --> I["从本地缓存取实例列表"]
I --> J["负载均衡选择实例"]为什么需要本地缓存:
| 原因 | 说明 |
|---|---|
| 性能 | 每次调用都查 Nacos 会增加远程开销 |
| 可用性 | Nacos 短暂不可用时,调用方还能用旧缓存 |
| 降低压力 | 注册中心不应该承受所有业务请求级查询 |
| 负载均衡 | LoadBalancer 基于本地实例列表选实例 |
为什么还需要推送或订阅:
| 原因 | 说明 |
|---|---|
| 实例变化更快感知 | 新实例上线、旧实例下线要尽快更新 |
| 降低轮询延迟 | 不只靠固定周期刷新 |
| 支持健康状态变化 | 不健康实例可从候选列表移除 |
注意:即使有推送,服务上下线也不是所有调用方瞬间一致。调用方本地缓存、连接池、网关路由、线程中的存量请求都存在传播窗口。
服务保护阈值和空列表保护
Nacos 服务发现不是简单地“健康就返回,不健康就一定不返回”。生产中还存在保护机制:当大量实例被判定不健康,注册中心可能为了避免全站瞬间无实例而触发保护;客户端收到异常空列表时,也可能保留最后一次可用快照。这些机制提高可用性,但会让调用方短时间继续看到旧实例或部分不健康实例。
flowchart TD
A["实例健康状态变化"] --> B["Nacos更新服务列表"]
B --> C{"是否触发保护"}
C -- "否" --> D["推送当前实例列表"]
C -- "是" --> E["保留更宽候选或旧快照"]
D --> F["调用方更新本地缓存"]
E --> F
F --> G["LoadBalancer选择实例"]小白要记住:保护机制不是保证“选中的实例一定健康”,而是防止“健康误判导致所有实例都被摘掉”。因此业务调用仍必须配置短超时、熔断、限流、降级、连接池回收和写请求幂等。
更深入的保护阈值、空推送保护、旧实例窗口和JDK 8 Demo见:保护阈值与空列表保护。
Nacos 和 LoadBalancer 调用链路
flowchart TD
A["业务代码调用 Feign"] --> B["Feign 动态代理"]
B --> C["解析服务名 asset-service"]
C --> D["LoadBalancer 获取候选实例"]
D --> E["Nacos 本地实例缓存"]
E --> F["按 namespace/group/cluster 过滤"]
F --> G["选择一个实例"]
G --> H["HTTP Client 调用 IP:Port"]所以“注册中心有实例”不等于“调用方一定能调用到”。可能的过滤条件包括:
- Namespace 不一致。
- Group 不一致。
- Cluster 不一致或同集群优先。
- 实例不健康。
- 权重为 0。
- 灰度 metadata 不匹配。
- 本地缓存还没刷新。
- 连接池还在复用旧连接。
配置中心定位模型
Nacos Config 通过三个核心字段定位配置:
Namespace + Group + DataIdflowchart TD
A["应用启动"] --> B["确定 Namespace"]
B --> C["确定 Group"]
C --> D["确定 DataId"]
D --> E["从 Nacos 拉取配置内容"]
E --> F["加载到 Spring Environment"]示例:
| 字段 | 示例 |
|---|---|
| Namespace | prod |
| Group | DEFAULT_GROUP |
| DataId | asset-service-prod.yml |
如果任何一个字段不一致,应用就可能拉不到配置。
DataId 常见命名
不同版本 Spring Cloud Alibaba 的加载规则和配置方式会有差异,但工程上通常建议 DataId 和应用名、环境、后缀保持清晰关系。
常见方式:
${spring.application.name}.${file-extension}
${spring.application.name}-${profile}.${file-extension}例如:
asset-service.yml
asset-service-prod.yml命名不要混乱,否则会出现“控制台明明有配置,但应用就是没读到”的问题。
配置启动拉取流程
flowchart TD
A["应用启动"] --> B["读取本地 bootstrap 或 application 配置"]
B --> C["拿到 Nacos 地址、namespace、group、文件后缀"]
C --> D["连接 Nacos Config"]
D --> E["按 DataId 拉取远程配置"]
E --> F["合并到 Spring Environment"]
F --> G["自动配置和业务 Bean 读取配置"]为什么配置中心要在早期加载?因为很多自动配置条件和 Bean 创建依赖配置。例如数据源地址、Redis 地址、线程池参数、开关配置,如果等业务 Bean 创建完才加载,已经太晚了。
接入示例:
spring:
application:
name: asset-service
profiles:
active: prod
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
namespace: prod
group: DEFAULT_GROUP
file-extension: yml
discovery:
namespace: prod
group: DEFAULT_GROUPNacos 中配置:
asset:
collect:
enabled: true
batch-size: 500
timeout-ms: 3000配置属性类:
@Data
@ConfigurationProperties(prefix = "asset.collect")
public class AssetCollectProperties {
private boolean enabled = true;
private int batchSize = 200;
private int timeoutMs = 3000;
}自动配置或业务配置:
@Configuration
@EnableConfigurationProperties(AssetCollectProperties.class)
public class CollectConfig {
@Bean
public CollectClient collectClient(AssetCollectProperties properties) {
return new CollectClient(
properties.getBatchSize(),
properties.getTimeoutMs()
);
}
}配置动态刷新原理
应用启动后,Nacos Client 会监听配置变化。配置变更后,客户端收到变更通知或通过监听机制发现变化,再拉取最新配置,更新到 Spring 环境中。
flowchart TD
A["运维修改 Nacos 配置"] --> B["Nacos 保存新配置"]
B --> C["通知或触发客户端发现变更"]
C --> D["客户端拉取新配置"]
D --> E["更新 Environment"]
E --> F["检查 Bean 的绑定与刷新方式"]
F --> G["可刷新则应用新值<br/>不可刷新则仍持有旧值"]不是所有配置都适合动态刷新。
| 配置类型 | 是否适合动态刷新 | 说明 |
|---|---|---|
| 功能开关 | 适合 | 例如是否开启采集 |
| 限流阈值 | 适合 | 调整后要配合监控 |
| 批处理大小 | 谨慎 | 可能影响吞吐和下游压力 |
| 线程池核心参数 | 谨慎 | 要确认线程池实现支持动态修改 |
| 数据源地址 | 不建议直接刷新 | 连接池、事务、旧连接处理复杂 |
| MQ Topic/Group | 不建议随意刷新 | 消费组变化可能引发重复消费或漏消费 |
@RefreshScope 示例:
@RefreshScope
@RestController
public class CollectSwitchController {
@Value("${asset.collect.enabled:true}")
private boolean enabled;
@GetMapping("/collect/enabled")
public boolean enabled() {
return enabled;
}
}更推荐把配置绑定到配置类,并明确哪些配置支持运行期变更,哪些必须重启。
配置发布为什么要有流程
配置中心让改配置变容易,也让线上事故变容易。
错误示例:
asset:
collect:
batch-size: 50000如果采集批大小从 500 改成 50000,可能导致:
- 单批 SQL 太大。
- 下游医院接口超时。
- JVM 内存上涨。
- MQ 消息变大。
- 数据库锁时间变长。
- 采集任务失败重试形成雪崩。
生产配置变更应该有:
| 能力 | 说明 |
|---|---|
| 权限控制 | 不是所有人都能改生产配置 |
| 审批 | 高风险配置要审批 |
| 变更记录 | 谁在什么时候改了什么 |
| 灰度 | 先对少量实例或小流量生效 |
| 回滚 | 出问题能快速恢复旧配置 |
| 监控 | 配置变更后观察错误率、耗时、QPS |
Nacos 高可用
生产环境不要单节点 Nacos。
flowchart TD
A["应用实例"] --> B["Nacos 三节点集群"]
B --> C["节点 1、节点 2、节点 3"]
C --> D["共享数据库与备份"]生产建议:
- 至少三节点部署。
- 使用外部数据库保存配置和元数据。
- 数据库要备份。
- 管理端账号不能使用默认弱口令。
- 控制台不要暴露公网。
- 不同环境最好独立 Namespace,重要环境也可独立集群。
- 监控 Nacos 节点 CPU、内存、连接数、请求耗时、数据库连接。
Nacos 是基础设施,不能让它成为单点。
Nacos 和 Eureka 对比
| 对比项 | Eureka | Nacos |
|---|---|---|
| 生态 | Spring Cloud Netflix | Spring Cloud Alibaba |
| 注册发现 | 支持 | 支持 |
| 配置中心 | 不提供 | 提供 |
| 命名空间 | 能力较弱 | Namespace/Group/Cluster 更常用 |
| 健康检查 | 客户端心跳、自我保护 | 支持临时/永久实例和健康检查 |
| 新项目常见度 | 老项目维护常见 | Alibaba 生态新项目常见 |
| 设计重点 | 可用性、客户端缓存 | 注册发现 + 配置管理一体化 |
不要只说“Nacos 替代 Eureka”。更准确是:Nacos 不只覆盖注册发现,还覆盖配置中心能力,平台职责更重,因此权限、审计、备份、发布流程也更重要。
Nacos 和 Spring Cloud Config / Apollo 对比
| 对比项 | Nacos Config | Spring Cloud Config | Apollo |
|---|---|---|---|
| 注册发现 | 内置 Nacos Discovery | 不提供 | 不提供注册中心 |
| 配置管理 | 提供 | 提供,常配 Git | 提供,配置治理成熟 |
| 动态刷新 | 支持 | 支持,常配 Bus | 支持 |
| 权限和发布治理 | 需要按平台能力建设 | 依赖配套平台 | Apollo 在配置治理上较强 |
| 适合 | Spring Cloud Alibaba 一体化 | Spring 官方生态、Git 管理 | 配置治理要求较高的企业 |
选型要看团队生态,不要只看单点功能。
商业场景:医疗数据采集平台
医疗数据采集平台里,Nacos 常用于:
| 场景 | Nacos 能力 |
|---|---|
| 采集服务发现资产服务 | Discovery |
| Gateway 路由到服务 | Discovery + LoadBalancer |
| 医院接口地址 | Config |
| 采集开关 | Config 动态刷新 |
| 采集批大小 | Config,谨慎动态调整 |
| 限流阈值 | Config + Sentinel |
| 灰度版本 | Instance metadata |
| 多环境隔离 | Namespace |
调用链路:
flowchart TD
A["采集任务进入 collect-service"] --> B["读取医院接口、批大小和开关"]
B --> C["Feign 按服务名发起调用"]
C --> D["LoadBalancer 读取本地实例列表"]
D --> E["选择 asset-service 实例"]
E --> F["HTTP 调用真实地址"]配置变更链路:
flowchart TD
A["运维调整采集开关"] --> B["提交 Nacos 配置"]
B --> C["记录审批和变更"]
C --> D["应用监听到配置变化"]
D --> E["刷新支持动态变更的 Bean"]
E --> F["采集任务按新开关执行"]
F --> G["监控成功率和耗时"]生产排查:服务发现不到
现象:Feign 报 No instances available for asset-service。
flowchart TD
A["No instances available"] --> B["核对服务名、Namespace、Group"]
B --> C["核对控制台健康实例与注册 IP"]
C --> D["核对客户端缓存是否收到更新"]
D --> E["核对 Cluster、Metadata、权重过滤"]
E --> F["恢复后用真实调用验证"]这是一条排查顺序,不表示所有步骤都正常后才进入下一步。发现某项不一致就先修复并重新验证;每一步要检查的证据见下表,深层日志与恢复命令见服务发现 Runbook。
排查清单:
| 检查项 | 说明 |
|---|---|
| 服务名 | @FeignClient(name) 和 spring.application.name |
| Namespace | 提供者和消费者是否同环境 |
| Group | 服务分组是否一致 |
| Cluster | 是否同集群优先导致过滤 |
| Healthy | 实例是否健康 |
| Weight | 权重是否为 0 |
| IP 可达 | 注册 IP 是否能从调用方访问 |
| 本地缓存 | 调用方是否已刷新实例列表 |
生产排查:配置不生效
现象:Nacos 控制台有配置,但应用读不到。
flowchart TD
A["配置不生效"] --> B["核对 DataId、Group、Namespace"]
B --> C["核对启动期导入方式和客户端日志"]
C --> D["核对 Environment 中的值与来源"]
D --> E["核对属性绑定、刷新范围与类型转换"]
E --> F["排除环境变量和本地配置覆盖"]配置排查必须区分“客户端没有拿到配置”“Environment 中已有新值”“业务 Bean 仍持有旧值”三个层次。逐层取证方法见配置 Runbook,不要只通过重启来掩盖根因。
常见原因:
| 原因 | 表现 |
|---|---|
| DataId 拼错 | 控制台有配置但应用找不到 |
| file-extension 不一致 | yml/yaml/properties 混乱 |
| Namespace 错 | dev 应用读 prod 或反过来 |
| Group 错 | 同 DataId 不同 Group |
| Profile 未激活 | 读了默认配置而不是环境配置 |
| 本地配置覆盖 | 最终 Environment 中不是 Nacos 值 |
| 配置类未注册 | @ConfigurationProperties 没生效 |
| 类型转换失败 | 字符串转数字、时间、集合失败 |
生产排查:修改配置后不刷新
先判断这个配置是否应该动态刷新。
| 检查项 | 说明 |
|---|---|
| 客户端是否监听配置 | 日志里是否有配置变更记录 |
| Bean 是否支持刷新 | 是否使用 @RefreshScope 或动态配置机制 |
| 配置读取方式 | 构造方法里读一次的值不会自动变 |
| 配置类型 | 数据源、线程池、MQ 这类复杂 Bean 不建议随便刷新 |
| 是否有多实例 | 是否所有实例都收到变更 |
很多配置“不刷新”不是 Nacos 问题,而是代码把配置在启动时读取成普通字段,后续没有刷新机制。
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| Namespace 混乱 | 服务发现不到、配置读错环境 | dev/test/prod 清晰隔离 |
| Group 随便改 | 提供者和消费者互相不可见 | 统一分组规范 |
| DataId 命名混乱 | 配置找不到 | 应用名 + profile + 后缀 |
| 配置中心无审批 | 生产误改配置 | 权限、审批、审计、回滚 |
| 动态刷新所有配置 | 连接池、线程池状态不可控 | 只刷新适合动态调整的配置 |
| Nacos 单节点 | 基础设施单点故障 | 三节点 + 数据库备份 |
| 注册不可达 IP | 控制台有实例但调用失败 | 注册调用方可访问地址 |
| 把配置当数据库 | 频繁存业务数据 | 配置中心只放配置,不放高频业务状态 |
独立面试入口
面试标准回答、追问和源码原理跳转已拆分到 Nacos独立面试题。知识点页只保留完整原理、Demo、商业场景和排查步骤,避免标准回答与课程正文混在一起。
关联知识点
| 知识点 | 跳转 |
|---|---|
| Spring Cloud 主线 | Spring Cloud 从零到生产级掌握 |
| Eureka | Eureka 注册发现 |
| 服务调用链路 | 服务调用链路 |
| 负载均衡 | Ribbon 与 LoadBalancer |
| 配置中心 | 配置中心 |
| Gateway | Gateway |
| Nacos面试题 | Nacos独立面试题 |
| Spring Cloud面试主线 | Spring Cloud 面试知识点 |
本章小结
Nacos 不只是“控制台里能看到服务”。注册发现要理解服务名、实例、心跳、健康状态、订阅推送、本地缓存和负载均衡;配置中心要理解 Namespace、Group、DataId、启动拉取、动态监听、刷新边界和配置发布治理。真正到商业项目里,Nacos 的难点不只是接入,而是环境隔离、命名规范、权限审计、高可用、配置变更流程和线上排查。
