ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Nacos服务注册与发现实战:从原理到配置,彻底搞懂注册中心

Nacos服务注册与发现实战:从原理到配置,彻底搞懂注册中心 1. 从“手动记IP”到“自动发现”服务注册与发现到底解决了什么问题先说个场景。早些年做单体应用的时候服务就一个部署在固定的服务器上IP 和端口写死在配置文件里一年到头也改不了几次。那时候根本不需要什么注册中心把配置配好、启动脚本写好就完事了。但随着业务拆分、微服务化推进情况开始变得完全不同。一个订单服务可能要调用用户服务、库存服务、支付服务每个服务又可能部署了三五个实例。如果某个实例挂了或者为了应对大促临时扩容了两个节点你打算怎么通知调用方靠人工改配置文件再重启吗在实例数量多、变动频繁的时候这条路根本走不通。我之前维护过一个系统每次发版前光改服务地址配置就要花上半小时还容易改错线上出了问题第一反应就是“是不是 IP 又写错了”。这就是服务注册与发现要解决的痛点在分布式环境下服务实例的数量和位置是动态变化的需要一个中间角色把这些信息统一管理起来——谁在提供服务、在哪里提供、是否还活着。有了它服务调用方不再需要关心对端实例的具体 IP 和端口只需要告诉注册中心“我要找哪个服务”注册中心就会把当前可用的实例列表告诉它。Nacos 就是干这个的。作为阿里开源的一个动态服务发现、配置管理和服务管理平台它最核心的能力之一就是这套服务注册与发现的机制。虽然现在注册中心的选项不少比如 Consul、Zookeeper、Eureka但 Nacos 在国内的使用面非常广尤其是结合 Spring Cloud Alibaba 和 Dubbo 生态的时候整合成本很低功能也更贴合实际业务需求。这篇文章是 Nacos 核心功能系列的第一篇我重点把服务注册与发现这条主线讲透。适合这些朋友阅读正在做微服务改造、需要为团队选型注册中心的技术负责人已经把 Nacos 用起来了、但对底层原理一知半解、面试时容易被问住的开发以及想从零搭建一套完整微服务 Demo 的初学者。我会从核心概念讲到注册表的底层数据结构再讲到真实的配置过程和常见坑尽量让你看完之后不光会用还能说出它为什么这样设计。2. 先搞懂几个核心概念实例、服务、命名空间、分组在动手配置之前我建议先把 Nacos 里几个基础概念捋清楚。这些概念如果你不搞明白后面配 namespace、group 的时候大概率会懵而且一旦配错问题还特别隐蔽——服务明明注册上了但调用方就是找不到。2.1 服务与实例一对多的关系“服务”是一个逻辑概念比如订单服务、用户服务。而“实例”是服务的一个具体运行进程它监听某个 IP 和端口真正处理请求。举个例子用户服务部署了 3 个节点那么从 Nacos 的视角看这就是一个名为 user-service 的服务下面挂了 3 条实例记录。每条记录包含实例 IP、端口、健康状态、权重、元数据等信息。调用方请求时Nacos 会把当前健康的实例列表返回给它由调用方根据自己的负载均衡策略选一个来发起请求。需要注意的一点是虽然我们平时说“服务注册”实际上注册的基本单位是实例而不是服务本身。服务是这些实例的逻辑聚合这个区分在理解 Nacos 的设计时有帮助。2.2 命名空间环境隔离的第一道闸门命名空间namespace是 Nacos 里做环境隔离用的。你可以创建 dev、test、prod 三个命名空间每个命名空间下的服务和配置都是相互隔离的。隔离的意思是dev 环境里的服务 A 去找服务 B 的时候只会找到 dev 命名空间下的 B 实例不会串到 prod 去。这里必须提醒一个容易踩的坑Nacos 默认有一个 public 命名空间它的 ID 是空字符串。很多人在控制台里看到 public 的命名空间 ID 是空的就以为不用配置结果手动填了别的 namespace ID 之后服务注册到了一个新建的命名空间控制台默认视图还是 public于是怎么找都找不到自己注册的服务。2.3 分组同一环境内的逻辑分区分组group是在命名空间之下的另一个维度默认值是 DEFAULT_GROUP。分组可以理解成同环境内的逻辑分区。比如你可以在同一个 dev 命名空间里把订单服务放到 DEFAULT_GROUP把新改造的订单服务放到 NEW_GROUP让一部分调用方先切到新分组做灰度验证。服务间调用时服务名 分组名 命名空间 ID 三者一起构成一个完整的目标标识缺一个都可能找不到服务。2.4 集群与地域多活部署的基础集群cluster这个概念平时容易被忽视但在多机房、多可用区部署的时候非常关键。每个实例注册时可以指定它属于哪个集群比如杭州机房、上海机房。调用方可以根据就近原则优先选择同一个集群内的实例降低跨机房调用的延迟和带宽成本。概念的部分就先讲到这里。实际配置的时候你会发现这些概念并不复杂但每个都对应着 Nacos 控制台里的一个入口也对应着客户端 SDK 里的一个配置项。理解它们之间的关系比死记参数重要得多。3. 底层原理拆解Nacos 的注册表结构、心跳机制与健康检查很多讲 Nacos 的文章一上来就让你写配置、启动服务但不解释底层原理。这样做的结果是配好了能跑起来一旦出了奇怪的问题完全没有排查思路。我始终认为注册中心的底层原理是排查问题的基础所以这一节我会重点拆解。3.1 注册表的核心数据结构为什么选 Map 而不选别的Nacos 服务端在内存中维护着一张“服务注册表”。简单来说它的核心结构大致是第一层 Mapkey 是 namespace IDvalue 是这一层 Map第二层 Mapkey 是 group 名字value 是下一层 Map第三层 Mapkey 是服务名value 是下一层 Map第四层 Mapkey 是集群名value 是下一层 Map第五层 Mapkey 是实例 IP 端口value 是实例详细信息五层 Map 嵌套构成了一个按 “命名空间 → 分组 → 服务 → 集群 → 实例” 逐级查找的结构。为什么用 Map因为注册中心的核心操作是“根据服务名找到对应实例集合”这是一个典型的 key-value 查询场景Map 天然适合。而且内存中直接查不需要每次都扫全表性能也有保障。有人会问为什么不直接用数据库呢因为注册中心对读写延迟要求极高服务启动时要注册下线时要注销实例变化时要通知订阅方这些操作如果都走数据库性能瓶颈和复杂度都会上去。Nacos 会把数据先放在内存里再通过异步的方式做持久化比如 Derby 或 MySQL保证 AP 可用性的同时尽量不丢数据。3.2 健康检查临时实例靠心跳持久实例靠主动探测这是 Nacos 设计里很有特色的一块。Nacos 将实例分为临时实例ephemeral和持久实例persistent两种的健康检查方式完全不同。临时实例是默认方式也是 Spring Cloud Alibaba 接入时默认的实例类型。它的健康检查依赖“心跳上报”客户端每隔 5 秒默认配置向服务端发送一次心跳服务端如果在 15 秒内没有收到心跳就会把这个实例标记为不健康如果超过 30 秒没收到直接剔除。注意这里的 5 秒、15 秒、30 秒分别是 Nacos 的“心跳间隔”、“超时时间”和“删除时间”这些参数在新版 SDK 中可以在客户端配置但正常情况下保持默认即可。持久实例则不同它不靠客户端上报心跳而是由 Nacos 服务端主动发起健康检查比如通过 HTTP 请求去探测实例的健康端点。这种方式更类似于传统注册中心如 Consul的做法适合那些不方便集成 Nacos SDK 的场景但服务端压力会更大一些。理解这两者的区别很重要。我见过有团队把所有实例都配成持久实例导致服务端健康检查线程暴增也见过有人把临时实例的心跳间隔调得特别短白白增加了网络开销。选型原则很简单能用临时实例就用临时实例它是 Nacos 最推荐的模式只有在客户端无法集成 SDK 时才考虑持久实例。3.3 服务发现Pull 与 Push 的微妙平衡服务发现有两种方式一种是客户端主动拉取Pull一种是服务端主动推送Push。Nacos 的策略是两者结合客户端启动时会主动拉取一次全量实例列表并缓存到本地。同时客户端会向服务端发起订阅当服务端感知到实例变化新增、剔除、不健康时会主动推送最新的实例列表给订阅的客户端。这里有个细节值得注意Nacos 1.x 时代的推送机制是 UDP存在丢包的可能后续版本增加了补偿机制和改造2.x 版本引入了 gRPC 双向流推送的可靠性大大提升。所以如果你在用 Nacos 1.x 并且遇到过服务列表刷新不及时的问题升级到 2.x 是一个合理的解决方向。3.4 CAP 权衡为什么 Nacos 在注册中心场景选了 AP关于注册中心选型AP 还是 CP 是个绕不开的话题。Nacos 的默认模式是 AP也就是说它优先保证可用性不做强一致。这意味着当集群出现网络分区时每个节点仍然可以对外提供读写服务但不同节点之间的数据可能暂时不一致。这个取舍在注册中心场景下是合理的——注册中心挂掉的影响远大于几个实例信息偶尔不一致的影响。实例列表短暂不一致最多就是调用的某个实例不存在、重试一次但如果注册中心因为要求强一致而拒绝服务整个微服务系统就全乱了。当然Nacos 也支持切换到 CP 模式使用 Raft 协议适合配置中心这类对一致性要求更高的场景。这一段讲完你应该对 Nacos 的设计有了一个比较清晰的认知它的目标是“服务数量多、变动频繁时让客户端能快速拿到尽量正确的实例列表”。基于这个目标它选择了内存多级 Map、临时实例心跳、推送结合拉取这套方案。理解了这些再去实操配置就会快很多。4. 环境准备下载、启动与集群部署的几种姿势原理部分讲完接下来进入实战环节。我尽量把步骤写细一点因为很多初学者卡住的点往往不是配置本身而是环境没弄对。4.1 单机模式开发调试最常用的方式首先从 GitHub 的 Nacos Releases 页面下载对应版本的压缩包。目前主流使用的是 2.x 版本具体版本号可以根据 Spring Cloud Alibaba 的版本兼容矩阵来选。下载完成后解压进入 bin 目录。单机模式下Linux/macOS 执行sh startup.sh -m standaloneWindows 执行startup.cmd -m standalone启动成功后访问http://localhost:8848/nacos默认账号密码都是nacos/nacos。这里要提醒一句生产环境务必要改默认密码Nacos 曾出过因为没有修改默认密码而被创建恶意用户的安全公告这个风险必须堵住。如果只是本地开发想快速验证单机模式足够了。但单机模式的数据是存在内置 Derby 数据库里的重启后会保留但本质上不具备高可用能力千万不要在生产环境用。4.2 Docker 部署一条命令跑起来如果你本地装了 Docker部署 Nacos 更方便。不过有两点需要注意第一是内存Nacos 默认的 JVM 参数比较大本地机器建议先限一下第二是网络模式。下面是我常用的一个最小启动命令docker run -d \ --name nacos-server \ -p 8848:8848 \ -p 9848:9848 \ -p 9849:9849 \ -e MODEstandalone \ -e JVM_XMS256m \ -e JVM_XMX512m \ nacos/nacos-server:v2.3.0这里面有两个端口很多人会忽略9848 和 9849。从 Nacos 2.x 开始客户端与服务端通信的主要方式变成了 gRPC而 gRPC 端口是在主端口 8848 的基础上自动偏移 1000也就是 9848客户端 gRPC和 9849服务端 gRPC。如果你用 Docker 映射端口时只暴露了 8848服务注册可能成功但服务发现和订阅推送会报错这个坑我踩过不止一次。4.3 集群模式的选型建议生产环境肯定要集群部署。Nacos 2.x 集群至少要 3 个节点才能组成合法集群官方推荐的部署方式是“3 个 Nacos 节点 1 个 MySQL”——用 MySQL 替换内置 Derby实现数据统一存储。集群的配置方式是在 application.properties 中配置数据库连接信息并在 cluster.conf 文件中列出所有节点地址。具体配置细节这里先不展开后续系列文章会单独写一篇集群部署的完整指导。这里先记住一个总体原则集群不是简单地把三个单机拼起来数据源、端口一致性、网络互通这些都要提前规划好。环境准备好之后下一步就是接入你的业务服务。5. 实战配置从 Provider 到 Consumer完整跑通一套注册发现这一节我以 Spring Cloud Alibaba 为例带着你从头到尾把服务注册与发现跑通。选择 Spring Cloud Alibaba 是因为它的接入方式最简单而且和 Nacos 的兼容性最好。如果你用的是 Dubbo核心思路类似只是注解和配置略有不同。5.1 版本选型先看兼容矩阵再动手版本不匹配是初学者最容易遇到的问题。Nacos、Spring Cloud、Spring Cloud Alibaba 三者之间有严格的版本对应关系用错了会遇到各种奇怪的报错。举个例子Spring Cloud Alibaba 2021.0.5.0 对应 Spring Cloud 2021.0.x、Nacos 2.2.0Spring Cloud Alibaba 2023.0.1.0 对应 Spring Cloud 2023.0.x、Nacos 2.3.2。建议以官方版本说明为准不要“随手拿个最新版”。我之前就遇到过 Spring Cloud 版本太高导致 Nacos 配置绑定失效的情况。下面是一组经过验证的版本组合可以直接参考组件版本Spring Boot2.7.18Spring Cloud2021.0.8Spring Cloud Alibaba2021.0.5.0Nacos Server2.2.0 / 2.3.x注意如果你用的是 Spring Boot 3.x 和 Spring Cloud 2023.x对应的 Nacos 客户端版本需要 2.3.2 以上。5.2 搭建服务提供者Provider创建一个 Spring Boot 工程引入核心依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency配置文件 application.ymlspring: application: name: order-service cloud: nacos: server-addr: 127.0.0.1:8848 # 命名空间如需隔离环境填写你在控制台创建的命名空间 ID # namespace: dev-namespace-id discovery: group: DEFAULT_GROUP启动类上加EnableDiscoveryClient注解新版其实可以不加但加上更明确SpringBootApplication EnableDiscoveryClient public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }启动后去 Nacos 控制台的“服务管理 → 服务列表”页面如果能看到 order-service 且实例数是 1、状态是健康那就注册成功了。5.3 搭建服务消费者Consumer并完成调用消费者同样引入 Nacos Discovery 依赖。在启动主类上添加EnableDiscoveryClient和负载均衡相关配置。用 RestTemplate 时只需要让注入的 RestTemplate 带有LoadBalanced注解Configuration public class RestTemplateConfig { Bean LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }这时你不需要在代码里写死目标服务的 IP 和端口只用服务名就能发起请求RestController public class OrderController { Autowired private RestTemplate restTemplate; GetMapping(/call) public String callUserService() { // 这里直接使用服务名而不是 IP:端口 return restTemplate.getForObject(http://user-service/user/info, String.class); } }LoadBalanced的底层原理是RestTemplate 在发起 HTTP 请求时会经过拦截器把请求 URL 中的服务名解析成具体的 IP:端口。解析过程就是先问 Nacos 拿到实例列表再根据负载均衡策略选一个。这里用服务名的调用方式就是注册发现机制真正起作用的体现。5.4 通过控制台快速验证注册结果服务起来之后验证方式不只有看控制台这一种。如果你在 Ubuntu Server 这类无桌面环境上不方便打开浏览器可以用命令行验证curl -X GET http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceNameorder-service返回的 JSON 里会带hosts数组里面就是当前注册的实例 IP 和端口。这个方法在排查“服务到底注册上没有”时非常有用。5.5 本地 namespace 配置的完整实操流程热词里有人在问“nacos 怎么配置本地的 namespace”这里详细说一下。先到 Nacos 控制台的“命名空间”页面点击新建填一个命名空间名称和命名空间 ID。命名空间 ID 可以理解成唯一标识建议用 UUID 或者可读性的英文标识比如 dev。然后在你的服务配置文件里加上spring: cloud: nacos: discovery: namespace: dev这里有个非常容易搞混的点控制台里展示的命名空间信息列表里有“命名空间 ID”和“命名空间名称”两列。你配置的时候填的一定是命名空间 ID而不是名称。很多人填了名称结果服务注册到了 public 或者莫名多了一个命名空间控制台怎么切都看不到数据就是这个原因。6. 高频报错与排查实战那些年见过的注册发现“疑难杂症”自己在接入 Nacos 的过程中踩过不少坑也在社群里见过各种奇怪的问题。这一节我把最高频的几个问题整理出来每一个都附上排查思路希望能帮你省下一点时间。6.1 服务注册了但控制台服务列表不显示这个问题出现频率极高原因通常有三个第一服务可能注册到了错误的环境。如果服务端连的 Nacos 和浏览器访问的 Nacos 不是同一个实例比如本地服务连着测试环境浏览器打开的却是本地自然看不到。先确认 server-addr 指向是否一致。第二命名空间和分组不匹配。控制台当前选的命名空间是 public但服务注册到了 dev 命名空间或者 group 不是 DEFAULT_GROUP。排查方法是在控制台切换命名空间和分组再查看。第三实例被标记为不健康。这种情况服务列表里能看到服务名但实例数为 0 或者状态是红叉。这通常是心跳没送到网络不通、防火墙拦了端口、客户端版本与服务端版本不兼容都可能引发这个现象。6.2 the spring.config.import property is missing a nacos entry这个报错很经典原因是 Spring Cloud 2020 版本之后配置中心的加载方式改变了。Nacos Config 不再默认自动加载需要显式在配置文件里加上导入配置spring: config: import: nacos:order-service.yaml这里的order-service.yaml是你在 Nacos 配置中心里的 dataId 后缀形式。如果暂时不需要配置中心功能只做服务注册和发现可以不引入 Nacos Config 依赖只保留 Discovery 依赖就能绕过这个限制。6.3 IPv4 地址识别不到注册的实例 IP 变成 IPv6在部分服务器上Nacos 客户端获取到的本机 IP 可能是 IPv6 地址或者取了一个错误的网卡地址导致服务注册的 IP 不对其他服务根本访问不到。解决方法是在客户端配置中指定网卡和 IPspring: cloud: nacos: discovery: ip: 192.168.1.100 network-interface: eth0如果不确定哪个网卡是目标网卡可以先执行ip addr查看本机网卡列表再选择正确的接口名。还有一种可能是服务器开启了 IPv6 优先策略可以尝试调整 JVM 启动参数-Djava.net.preferIPv4Stacktrue强制优先 IPv4 协议栈。6.4 服务名解析失败UnknownHostException 与 InstanceNotFound发起调用时如果报UnknownHostException说明服务名没有解析成可访问的地址。先确认调用方是否引入了 Discovery 依赖有没有加LoadBalanced再确认被调服务有没有成功注册可以用 5.4 节的 curl 命令查一下目标服务名下的实例列表。如果报的是InstanceNotFound则说明 Nacos 上确实查不到这个服务优先排查服务名是否一致。很多时候是调用方写了user-service而注册方注册的是user_service一个横杠的区别排查起来非常痛苦。6.5 命名空间一直为 null 的诡异现象有朋友遇到过“控制台里命名空间明明配了但服务列表里 namespace 一直显示为 null”的问题。这个问题大概率是服务端的数据存储出了问题。Nacos 的命名空间信息是存在配置存储表里的当使用了 MySQL 作为数据源时如果nacos_tenant表没有初始化好或者权限不对就会导致命名空间读取异常。这时建议先把数据源切换到内置 Derby 验证一下排除代码和配置层面的问题再回到 MySQL 的初始化脚本和数据连接权限上找原因。6.6 常见问题排查速查表问题可能原因优先排查点控制台看不到服务命名空间/分组不对切换控制台命名空间核对配置文件 namespace 字段实例状态不健康心跳未送达检查 8848、9848 端口连通性服务名解析失败服务名不一致对比注册方和调用方服务名注意大小写和横杠调用超时实例 IP 注册成了内网或错误 IP检查 discovery.ip 配置和网卡选择配置中心报 missing nacos entrySpring Cloud 版本过新使用 nacos config import 语法或去掉配置中心依赖客户端日志大量重连网络不稳定或版本不兼容升级 Nacos Server 到 2.x 并使用对应版本的 client7. 选型对比与实践总结从 Nacos 到其他注册中心关键差异一览聊到这里服务注册发现的核心内容基本覆盖了。最后这块内容是我个人的一些观察和选型实践不一定适合所有团队但希望给你提供一个参考维度。Nacos 和其他注册中心相比最大的优势在于“服务注册发现 配置管理”二合一。尤其在国内的技术栈下Dubbo 和 Spring Cloud Alibaba 都对 Nacos 做了深度适配资料和解决方案也比较好找。Eureka 虽然稳定但已经停止维护Consul 在功能上很强但国内社区相对小众Zookeeper 作为注册中心在性能和可用性上也有短板。如果你们团队还没有深度绑定某一套技术栈Nacos 是性价比很高的选择。在接入策略上我有几条比较实在的建议。第一即使是小项目也建议从一开始就给服务划分命名空间至少把 dev 和 prod 分开否则后面做隔离要改一大堆配置。第二namespace 一旦确定就不轻易改调用方和服务提供方的 namespace 必须一致这个字段不像 group 那么好容错。第三生产环境的 Nacos 服务端建议关闭默认的匿名访问能力开启鉴权并且定期更新密码。根据我的实际经验接入 Nacos 服务注册发现本身并不复杂真正麻烦的是排查问题时的思路。当你看到一个注册发现相关的报错时先确定问题出在“注册”端还是“发现”端注册端看服务端的日志和控制台的实例状态发现端看客户端的日志和缓存。两侧日志都抓下来对比一下服务名、命名空间、分组这三个关键字段大概率能在五分钟内定位到问题。以后你在微服务这块碰到其他注册中心的选型或排查问题也可以顺着这个思路去分析。注册中心的核心始终是“实例信息的存储 健康检查 状态变更推送”这三件事不管是哪个组件万变不离其宗。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进