publish nacos metadata failed这个报错,我在过去几年的微服务项目里少说遇到过七八次,每一次的根因都不太一样。它最烦人的地方在于,异常信息本身给的信息量极低——客户端只会甩给你一句发布元数据失败,至于到底是网络不通、鉴权没配、命名空间不存在,还是服务端自己都没起来,全都要靠你自己一层层扒。更坑的是,这个问题在单体测试环境几乎不出现,一上容器、一上集群、一换数据库就开始冒头,很多人第一次遇到就是在预发或者生产,压力直接拉满。
这篇文章我打算把publish nacos metadata failed这条错误从里到外拆一遍。先讲清楚 Nacos 里的 metadata 到底是个什么东西、一次发布请求在客户端和服务端之间经过了哪些环节,再按环境层、配置层、客户端整合层三个方向分别展开排查思路,最后给一份能直接照着敲的命令清单和速查表。不管你是刚在 Windows 上装完 Nacos 单机版跑第一个 Demo 的新手,还是正在把注册中心往达梦数据库上迁、用 Rancher 部署集群的老手,应该都能在里面找到对得上的场景。
1. 搞懂 metadata 是什么,报错才不至于瞎猜
1.1 Nacos 里的 metadata 到底存了哪些东西
很多人看到 metadata 这个词会下意识以为是 Nacos 服务端某个内部表的字段,其实不是。客户端在调用注册接口时,会构造一个 Instance 实例对象,这个对象里除了 ip、port、weight、healthy、enabled、ephemeral 这些基础字段,还有一个Map<String, String> metadata。这个 Map 就是所谓的元数据,它是留给业务方自由扩展的键值对容器,Nacos 本身不关心里面装什么,只负责原样存储和原样返回。
实际项目里这个 Map 里常见的内容包括:Spring Cloud 自动塞进去的preserved.register.source(标记实例来自哪个框架)、灰度发布用的version或者tag、链路追踪用的zone、Kubernetes 环境下注入的k8s.pod.name,还有些团队会把自己的业务分组标记塞进去做路由。所以当你看到publish nacos metadata failed,本质上失败的不是某个叫 metadata 的独立动作,而是整个实例注册请求在一次提交中没能成功落到服务端,报错文案只是把 metadata 这个字段拎出来当了个代表。
理解了这一点,排查方向就清晰了:不要去 Nacos 的数据库里翻什么 metadata 表,而应该把注意力放在"这次注册请求为什么没送达、或者送达了为什么被拒绝"上。这两条路对应的是完全不同的排查手段,一个偏网络和进程,一个偏配置和权限。
1.2 一次 metadata 发布在客户端和服务端之间走了哪几步
Nacos 2.x 之后,客户端和服务端之间的通信协议从纯 HTTP 换成了 gRPC 长连接为主、HTTP 兜底的混合模式,这个变化是很多"以前好好的、升级后突然报错"的根源。一次注册请求完整走下来大致是这样:客户端启动时先通过 HTTP 请求 8848 端口拿到服务端地址列表和连接配置,然后向服务端的 gRPC 端口(默认是主端口加 1000,也就是 9848)建立长连接,之后所有的注册、订阅、心跳都走这条长连接。
如果是集群模式,服务端之间还要用 9849 端口互相通信做数据同步。所以真正需要打通的端口是三个:8848(HTTP API 与控制台)、9848(客户端 gRPC)、9849(服务端集群间 gRPC)。我见过太多次了,运维同学只放行了 8848,浏览器打开控制台一切正常,服务一启动就publish nacos metadata failed,查半天查不出所以然,最后发现是防火墙把 9848 拦了。
再往细看,发布失败还可能发生在服务端的处理链路里:请求到了服务端,要先做鉴权校验,再校验命名空间和分组是否存在,然后才写内存注册表、异步落库(如果开了持久化)、最后通知订阅者。任何一个环节抛异常,客户端收到的都是同一句话。这就是为什么单看客户端日志几乎没法定位,必须两端日志对照着看。
1.3 publish failed 的三层根因划分法
踩的坑多了之后,我习惯把所有publish nacos metadata failed的原因先粗暴地分成三层,然后再逐层排除,效率比漫无目的地翻日志高得多。
第一层是环境层,包括端口不通、服务端进程没起来或者启动失败了、容器网络里注册的 IP 不对、集群节点列表不一致。这一层的特征是:客户端连请求都没真正送出去,或者送出去被网络设备丢了,重试多少次都是一样的结果。
第二层是配置层,包括命名空间不存在、鉴权开关打开了但客户端没带账号、服务端连不上自己的数据库导致注册表写不进去。这一层的特点是:请求能到服务端,但服务端处理时报错拒绝,通常服务端日志里会有更明确的堆栈。
第三层是客户端整合层,包括 Spring Cloud Alibaba 版本和 Nacos 服务端版本不匹配、Dubbo 和 Spring Cloud 同时往一个服务上塞元数据、重复注册触发了服务端的限流或者覆盖逻辑异常。这一层最隐蔽,因为它往往表现为间歇性失败,重启一次可能就好了,过段时间又冒出来。
三层分完之后,从下往上查,环境层永远是第一步——因为如果端口都不通,你去改配置纯属浪费时间。这也是我后面章节展开的顺序。
2. 环境层排查:端口、网络、容器与集群
2.1 8848 通了不代表能注册,9848 才是重灾区
这是我最想强调的一条。判断 Nacos 服务端是否"可用",很多人用的标准是"浏览器能打开控制台",能打开就认为一切正常。但控制台走的是 8848 的 HTTP,而注册走的是 9848 的 gRPC,两者完全独立,一个通不代表另一个通。
排查方法很直接,在客户端所在的那台机器上执行端口探测。Linux 下用telnet 10.0.0.11 8848和telnet 10.0.0.11 9848分别试;如果机器上没有 telnet,用nc -zv 10.0.0.11 9848或者curl -v telnet://10.0.0.11:9848也行。Windows 上可以用 PowerShell 的Test-NetConnection -ComputerName 10.0.0.11 -Port 9848。注意一定要在客户端所在的机器上测,很多人在自己办公电脑上测通了就下结论,结果客户端跑在另一台隔离网段的机器上,完全是两回事。
如果 9848 不通,方向就很明确了:要么是中间有防火墙或者安全组规则没放行,要么是 Nacos 服务端配置里改了 gRPC 的偏移量。Nacos 有个配置项nacos.server.grpc.port.offset,默认值是 1000,如果你在服务端改了它,客户端也得跟着改,两边不一致同样会导致连不上。还有一种情况是 Nacos 2.x 部署在了只支持 HTTP 的负载均衡后面,七层代理不认识 gRPC 的长连接,这种场景要么换四层代理,要么老老实实把客户端直连到具体节点。
提示:升级到 Nacos 2.x 之后,记得同步更新所有安全组和防火墙规则,把 9848、9849 一起放行,别只加 8848。
2.2 Docker 与 Rancher 部署下最容易被忽略的 IP 注册问题
用 Docker 或者 Rancher 部署 Nacos 时,publish nacos metadata failed经常和网络模式绑定出现。典型现象是:Nacos 容器本身跑得好好的,控制台能打开,客户端却一直注册失败或者注册上去了但显示的是 172.17.0.x 这种内网地址,其他服务根本调不通。
根因在于容器默认的 bridge 网络模式下,Nacos 服务端会把容器的内部 IP 当成自己的地址返回给客户端,客户端拿到这个 IP 去连,自然连不上。解决办法是给 Nacos 容器显式指定它应该对外暴露的地址。如果你用 docker run,加环境变量NACOS_SERVER_IP指向宿主机或者负载均衡的 IP;如果用 docker-compose 或者 Rancher 的编排,就在环境变量里把NACOS_SERVER_IP、NACOS_SERVER_PORT都设清楚,端口默认 8848 不用改,但 IP 必须是对客户端可见的那个。
还有一种更常见的配置遗漏:在 Rancher 或者 Kubernetes 里部署 Nacos 时忘了配MODE=standalone。容器里如果不显式指定集群模式还是单机模式,Nacos 会默认按集群模式启动,然后去连其他节点试图组成集群,结果当然组不起来,注册请求就会失败。单节点部署务必加上MODE=standalone这个环境变量。我见过有人为了这个折腾了一整个下午,所有配置都对,就是这一句话没加。
另外容器部署时还要注意主机名解析。Nacos 2.x 的 gRPC 通信对 hostname 比较敏感,如果容器内/etc/hosts或者 DNS 解析出来的主机名客户端那边解析不了,也会间接导致发布失败。稳妥的做法是在集群配置里统一用 IP,别混着用主机名。
2.3 集群节点列表不一致导致的间歇性发布失败
单机部署很少遇到这个问题,但只要你部署了三个节点以上的集群,就要留意节点列表的一致性。Nacos 集群里每个节点都维护了一份集群成员列表,客户端启动时会拉取这份列表,然后按某种策略选一个节点注册。如果某个节点已经被下线了,但它还在其他节点的列表里,客户端就有可能把请求发到那个已经死掉的节点上。
判断方法是通过接口直接看服务端自己认的节点列表。访问http://10.0.0.11:8848/nacos/v1/core/cluster/nodes(不同版本路径可能略有差异,有的版本是/nacos/v1/ns/operator/servers),返回的 JSON 里会列出当前集群认为活着的节点。如果你发现这个列表里包含了已经下掉的机器,那基本就锁定问题了。修复方式是重启还在线的节点,让它们重新做一次成员协商;如果重启解决不了,检查cluster.conf文件里是否写死了旧节点。
间歇性失败的另一个诱因是服务端之间的数据同步延迟。三节点集群里如果某个节点负载很高,写入之后同步慢了,客户端恰好连到这个慢节点,也可能短暂拿不到刚注册的实例。这类问题通常表现为"过几秒又好了",配合监控看节点的 CPU 和 GC 情况一般能确认。生产环境建议至少三节点起步,别为了省机器搞双节点,双节点在脑裂判断上非常尴尬。
3. 配置与鉴权层:命名空间、账号和数据库适配
3.1 命名空间不存在与鉴权开关引发的静默失败
如果环境层排查没有发现问题,端口全通、服务端也活着,那就要往配置层看了。这一层里最高频的两种原因是命名空间和鉴权。
先说命名空间。Nacos 的命名空间是用一个 UUID 来标识的,客户端配置里如果写的是命名空间的名称而不是 ID,或者从别的环境拷贝配置时把 ID 带错了,服务端在收到注册请求时会因为这个 namespace 不存在而拒绝。这种拒绝有时候不会在客户端给出很详细的提示,只留一句publish nacos metadata failed。排查方式是在控制台里进【命名空间】页面,把目标命名空间的 ID 复制出来,逐字和客户端配置文件里的spring.cloud.nacos.discovery.namespace比对。特别注意公共命名空间的写法,它是空字符串或者public,不是某个 UUID,这个坑新手很容易踩。
再说鉴权。Nacos 默认是不开鉴权的,但生产环境出于安全考虑一般都会打开。开启鉴权之后,所有写操作都必须携带有效的身份凭证。如果客户端没配spring.cloud.nacos.username和spring.cloud.nacos.password,或者配的是默认的 nacos/nacos 但服务端已经改过密码了,注册请求就会被服务端直接拒绝。
注意:开启鉴权后,务必第一时间修改默认账号密码,并且检查控制台是否还会无条件跳转登录。命名空间级别的权限也要一并规划好,避免所有服务共用一个超级账号,出问题时排查范围和影响面都会失控。
这里还有个小细节,鉴权凭证在 Nacos 2.x 里是通过 gRPC 的 metadata(和业务 metadata 同名但完全不是一回事)携带的,如果你在客户端和 Nacos 之间架了一层代理,代理把 header 吃掉了,凭证传不到服务端,同样会表现为发布失败。所以排查鉴权问题时,最好让客户端直连服务端,先排除中间层干扰。
3.2 数据库适配(达梦、DB2 等)下的元数据写入异常
这是近几年越来越常见的一个场景。Nacos 默认用内嵌的 Derby 做单机存储,生产环境一般换成 MySQL,但有些项目因为信创要求会改用达梦、DB2 这类数据库。换数据库的时候,publish nacos metadata failed出现的概率会明显上升,原因基本都集中在 SQL 方言和表结构上。
Nacos 的持久化层并没有用完整的 ORM 框架,相当一部分 SQL 是手写的,分页、时间函数、字符串拼接这些地方对数据库方言有依赖。换成达梦之后,如果只是把 JDBC 驱动和连接串改了,源码层面没做适配,那么写注册表的时候就会抛 SQL 异常。典型表现是:服务能启动,控制台能看,但一有服务注册就失败,而且失败日志往往被截断,只看客户端什么都看不出来。
所以适配达梦这类数据库的正确姿势是:第一,先把官方 MySQL 的建表脚本按目标数据库的语法改写,字段类型、主键生成、索引这些都要过一遍;第二,编译一份适配版的服务端,把 SQL 方言相关的部分改成目标数据库支持的写法;第三,单独验证注册、订阅、配置发布这三条主链路,别只测配置中心。这几个步骤里,第二条最费时间,但恰恰是决定成败的一步。
如果你们用的还是若依那套微服务脚手架,注意它的ry-cloud主业务库和ry-config配置库是两个独立的库。Nacos 只应该连ry-config,业务服务连ry-cloud。这两个库的连接串如果搞混了,或者两个库都用同一个账号但权限没配全,也会在上层表现为各种奇怪的注册异常。我建议在切换数据库前,先把 Nacos 的数据库连接单独用一个最小权限账号验证一遍,跑通再说。
3.3 spring.config.import 没配导致的上游连锁反应
Spring Boot 2.4 之后,配置文件的加载机制改了,从 Nacos 拉配置需要显式声明spring.config.import。如果没声明,启动时会直接报no spring.config.import property has been defined这个错。这个错和publish nacos metadata failed表面上看没关系,但实际排查中经常连在一起出现——因为很多人的做法是"看到报错就注释掉相关配置",把配置中心这一整块关掉,结果注册这块的上下文也跟着丢了。
正确的做法是老老实实把 import 补上。以 Spring Cloud Alibaba 为例,spring.config.import可以写成optional:nacos:application.yml或者带命名空间的optional:nacos:xxx.yml?group=DEFAULT_GROUP&namespace=<uuid>,具体语法随版本略有差异,配的时候对着官方文档抄,别凭记忆写。补上之后,服务启动时会先去拉远程配置,再初始化注册逻辑,两条链路都正常了,注册才会稳。
顺带提一句配置的动态刷新。Nacos 的配置热更新靠的是客户端的长轮询加上@RefreshScope注解。如果你在注册失败的同时还观察到配置改了不起作用,那很可能是长轮询这条链路也断了,根因大概率还是回到 gRPC 连接本身,而不是配置写得不对。这时候别急着去改业务代码,先把连接问题解决掉。
4. 客户端与框架整合层:版本矩阵和 Dubbo 上报
4.1 Spring Cloud Alibaba 版本对不上,报错往往长得很像
环境通、配置对,但注册还是失败,这时候要重点怀疑版本兼容性。Spring Cloud Alibaba 和 Nacos 客户端、Nacos 服务端之间是有对应关系的,这个对应关系不是随便挑版本就能凑合的。举个常见的例子:Spring Cloud Alibaba 2021.x 对应的 Nacos 客户端是 1.4.x 或者 2.0.x,如果服务端升到了 2.2.3,而客户端还停留在很老的 1.x 版本,两者之间的协议差异会导致注册时报错,而报错的文案恰恰就是publish nacos metadata failed。
排查版本问题,最有效的方式是启动时打开 Debug 日志,把 Nacos 客户端实际使用的版本号打印出来,再和官方版本对应表对一遍。同时看服务端的启动日志,确认它监听的是哪套协议。两边版本对上了,再考虑别的方向。
还有一个容易忽略的点是依赖冲突。项目里如果同时引入了nacos-client和nacos-discovery的不同传递依赖,Maven 仲裁出来的版本不一定是你期望的那个。用mvn dependency:tree -Dincludes=com.alibaba.nacos把 Nacos 相关的依赖树打出来看一眼,比盯着 pom 文件猜要靠谱得多。
提示:升级 Nacos 服务端时,先把客户端依赖版本一起升,别只升一边。如果做不到同步升级,至少保证客户端不低于服务端支持的最低版本。
4.2 Dubbo 与 Nacos 混用时的元数据双重上报
有些项目是 Spring Cloud 和 Dubbo 混着用的,注册中心统一用 Nacos。这种架构下,publish nacos metadata failed有一个很特别的原因:两个框架都在往同一个实例上写元数据,而且写的内容和解读方式不一致。
Dubbo 在注册的时候会往 metadata 里塞它自己的接口方法列表、协议信息等内容,Spring Cloud 也会塞自己的东西。如果两边用的 instance 标识(就是 ip、port、serviceName 这三元组的组合)恰好撞上了,后注册的一方会覆盖前一方,而覆盖过程中如果触发了服务端的更新逻辑异常,客户端看到的也是发布失败。
解决思路是给两类服务明确的隔离:用不同的命名空间区分 Spring Cloud 服务和 Dubbo 服务,或者至少用不同的 group。别让它们挤在同一个 DEFAULT_GROUP 里。另外,Dubbo 那边如果配置了多个注册中心,检查一下是不是有一个配置项指到了一个已经废弃的地址,这种半死不活的配置最容易制造间歇性失败。顺便说,Dubbo 的元数据上报和 Nacos 的实例注册是两套机制,排查时别混为一谈,先确认是哪一套在报错。
4.3 动态刷新与热更新触发的重复注册
最后这一类原因比较隐蔽,和动态刷新有关。Nacos 支持配置热更新,业务上一般会配合@RefreshScope使用。如果某个 Bean 因为配置变更被重新创建,而这个 Bean 里恰好持有 Nacos 客户端的注册句柄,就有可能出现重复注册——同一个实例在极短时间内被注册了两次甚至多次。
服务端对重复注册本身是有幂等处理的,正常情况下不会报错。但如果短时间内请求量超过了服务端的处理能力,或者服务端开了写限流,后面的请求就会被拒绝,客户端收到的就是发布失败。这种失败的特点是:不是每次都出现,往往在发布配置、扩容、或者批量重启的时候集中爆发。
应对方式有两个方向。一是缩短配置刷新的影响范围,把和注册相关的 Bean 排除在@RefreshScope之外,注册这件事本身不需要动态刷新,实例信息变了走重新注册的路径更清晰。二是检查服务端的写限流参数,如果集群规模确实大,适当调整限流阈值,别用默认值硬扛。这两个方向配合着来,间歇性失败基本能压下去。
5. 完整排查流程与实测复现
5.1 五步定位法:从异常栈到服务端日志
前面讲了这么多原因,落到实操上,我整理了一套固定的五步排查顺序,按这个走基本不会漏。
第一步,拿到客户端最完整的异常栈。publish nacos metadata failed通常是被包装过的外层异常,真正的根因藏在Caused by里。日志框架如果做了精简,把Caused by丢了,就临时把 Nacos 客户端的日志级别调到 DEBUG,重新跑一次。这一步的目的是确定失败到底发生在连接阶段还是服务端拒绝阶段。
第二步,在客户端机器上探测三个端口。8848、9848 必须通,集群模式再加 9849。不通就停在环境层解决,通了再往下走。
第三步,登录 Nacos 控制台做三个确认:命名空间是否存在且 ID 对得上、当前实例所在分组有没有异常、鉴权是否开启以及账号是否有效。这三个确认能在控制台页面直接完成,不用敲命令。
第四步,看服务端的naming相关日志。Nacos 的日志目录下naming.log和naming-server.log是注册相关的,里面会记录每一次注册请求的处理结果。如果客户端说失败,而服务端日志里压根没有这条请求的记录,说明请求根本没到,问题在网络上;如果有记录但标记为失败,日志里通常带原因。
第五步,用 curl 直接调服务端的注册接口,绕开客户端。这一步相当于把问题二分:curl 能成功说明服务端没问题,问题在客户端;curl 也失败,问题就在服务端或者中间的网络上。具体命令我在下一节给出。
5.2 用 curl 手动打接口,把问题二分
Nacos 的 HTTP 注册接口是开放的,哪怕你的客户端走的是 gRPC,也可以先用 HTTP 接口验证服务端的基本能力。一条典型的注册请求长这样:
curl -X POST 'http://10.0.0.11:8848/nacos/v1/ns/instance' \ -d 'serviceName=test-service' \ -d 'ip=10.0.0.22' \ -d 'port=8080' \ -d 'namespaceId=public' \ -d 'groupName=DEFAULT_GROUP' \ -d 'metadata={"version":"1.0.0"}'如果这条命令返回ok,说明服务端的注册链路是通的,问题就落在了客户端的连接方式或者配置上,重点回查第三、四章的内容。如果返回的是错误码或者报错信息,那服务端这边就有问题,重点看数据库连接、磁盘空间、内存这几项。
如果想验证带鉴权的场景,先在控制台或者用登录接口拿到 accessToken,然后在请求里带上accessToken=xxx参数。注意 token 是有有效期的,别拿着过期的 token 测半天,最后误判。另外这条命令里我故意带了 metadata 参数,就是为了复现标题里的场景——如果连手动请求带 metadata 都失败,那基本可以确定是服务端对 metadata 的处理出了问题,比如内容过大、编码异常等。
再补一个思路,用curl -v看完整的请求响应过程,重点关注响应头里的状态码。401 对应鉴权,403 对应权限不足,500 对应服务端内部异常。状态码能帮你快速缩小范围,比读堆栈快得多。
5.3 一份可以直接抄的排查命令清单
把上面散落的命令集中列一份,出问题的时候直接按顺序敲,能省不少时间。这些命令在 Linux 和 macOS 上都能跑,Windows 用户把端口探测换成Test-NetConnection、把grep换成findstr即可。
# 1. 端口探测(在客户端机器上执行) nc -zv 10.0.0.11 8848 nc -zv 10.0.0.11 9848 # 2. 查看服务端集群节点列表 curl -s 'http://10.0.0.11:8848/nacos/v1/core/cluster/nodes' | python -m json.tool # 3. 手动注册一个测试实例 curl -X POST 'http://10.0.0.11:8848/nacos/v1/ns/instance' \ -d 'serviceName=test-service' -d 'ip=10.0.0.22' -d 'port=8080' # 4. 查询刚注册的实例,确认是否落库 curl -s 'http://10.0.0.11:8848/nacos/v1/ns/instance/list?serviceName=test-service' | python -m json.tool # 5. 抓包看 gRPC 连接是否建立(需要 root) tcpdump -i any -nn port 9848 -c 20第 4 条命令特别值得说。很多人只验证注册成功,不验证查询,结果注册接口返回了 ok,但查询查不到,说明写内存成功了但持久化失败,或者查的是另一个命名空间。注册和查询成对验证,才能确认整条链路真的通了。
第 5 条抓包命令的用法是:先启动抓包,再启动客户端。如果抓到的包里只有 SYN 没有 ACK,说明 TCP 都没建立,网络层的问题;如果有完整的三次握手但没有应用层数据,可能是 TLS 或者协议不对;如果应用层有数据但很快断开,重点看服务端日志。抓包这一步稍微有点门槛,但在网络策略复杂的环境里,它往往是唯一能给出确定答案的手段。
6. 常见问题速查表与踩坑心得
6.1 publish failed 高频原因速查表
下面这张表是我根据实际处理过的案例整理的,按出现频率从高到低排列,遇到问题先对照着过一遍,能过滤掉大部分情况。
| 现象特征 | 大概率原因 | 第一步验证动作 | 处理方式 |
|---|---|---|---|
| 控制台能开,客户端注册失败 | 9848 gRPC 端口未放行 | 在客户端机器nc -zv ip 9848 | 放行防火墙/安全组 9848、9849 |
| 容器部署,实例 IP 是 172.17.x.x | 容器网络回传内部 IP | 控制台查看实例列表的 IP | 设置NACOS_SERVER_IP为外部可达地址 |
| 单节点容器启动后注册失败 | 未指定 standalone 模式 | 查看服务端启动日志的模式 | 加环境变量MODE=standalone |
| 提示命名空间相关信息 | namespace ID 写错或不存在 | 控制台命名空间页复制 ID 比对 | 修正discovery.namespace配置 |
| 服务端日志报鉴权失败 | 账号密码错误或未配置 | 用 curl 带 token 调注册接口 | 补全 username/password 并改默认密码 |
| 换达梦/DB2 后注册全挂 | SQL 方言未适配 | 手动注册并看服务端堆栈 | 改写建表脚本并编译适配版服务端 |
| 升级 Nacos 后开始报错 | 客户端与服务端版本不匹配 | 打印客户端实际版本号 | 同步升级客户端依赖 |
| 发布配置时集中报错 | 动态刷新触发重复注册 | 查看是否有@RefreshScope包裹注册 Bean | 排除注册相关 Bean,调整写限流 |
| 集群中偶发失败,过会自愈 | 节点列表含已下线节点 | 调/nacos/v1/core/cluster/nodes | 重启在线节点,清理 cluster.conf |
| 服务端日志无对应请求记录 | 请求未到达服务端 | 抓包看 9848 连接 | 排查中间代理和链路设备 |
表格里的"第一步验证动作"这一列是我特意加的。排查这件事最怕的就是上来就改配置,改了一圈也不知道到底是哪个改动生效了。养成先验证、再动手的习惯,每次只改一个变量,问题定位会快很多。
6.2 我在生产环境踩过的几个坑
说几个具体的经历,都是文档里不太会写的东西。
第一个坑是关于升级顺序的。有一次我们把 Nacos 服务端从 1.4 直接升到 2.2.3,升完之后大部分服务正常,但有两个老服务一直报publish nacos metadata failed。查了半天发现这两个服务锁死在一个很老的 Spring Cloud Alibaba 版本上,短期内没法升级。最后采取的办法是在 Nacos 服务端保留 HTTP 兼容能力,同时给这两个服务单独配了一个 1.x 的客户端依赖,用独立的依赖管理隔离开。所以升级前一定要把客户端的版本分布摸清楚,别只盯着服务端。另外提醒一句,Nacos 2.2.3 这类较新版本对 JDK 版本也有要求,别在 JDK 8 的老环境上硬上。
第二个坑是关于达梦适配的。我们做信创改造时,最开始想的是"只改驱动不改代码",结果上线前压测就发现注册接口大批量失败。根因是 Nacos 内部有几处 SQL 用了 MySQL 特有的写法,达梦虽然兼容度不错,但这些地方还是过不去。最后的方案是拉了一份社区适配版的分支,把方言相关的代码重新过了一遍,前后花了差不多两周。这个过程给我的教训是:数据库适配这件事,工作量永远比预估的大,排期的时候要留足缓冲,测试要覆盖注册、订阅、配置发布、集群同步四条链路。
第三个坑是关于 Rancher 部署的。我们在 Rancher 上跑 Nacos 集群,一开始是三个副本,外部访问始终有问题,客户端注册时好时坏。后来发现是 Service 的类型和端口映射没配对,gRPC 流量被转发到了随机的副本上,而长连接又要求粘性。改成每个节点单独暴露端口,客户端配置里写死节点列表之后,问题就没了。所以集群部署在容器平台上时,网络这块一定要专门验证一遍,别想当然地认为编排工具会自动处理好。
第四个坑比较小但很典型:磁盘空间。有一次注册突然大面积失败,所有排查方向都试过没用,最后发现是 Nacos 所在机器的磁盘满了,日志都写不进去了。这类问题平时不会想到,但它确实会发生。建议把 Nacos 数据目录和日志目录的磁盘使用率纳入监控,设置一个 80% 的告警阈值,比事后救火强得多。
最后分享一个我自己一直在用的习惯:在项目里维护一个nacos-troubleshooting.md,每次遇到新的注册失败案例,就把现象、根因、解决方式补进去。时间长了这份文档会变成团队里最有价值的东西之一,新人遇到问题先查它,能省下大量重复沟通的成本。publish nacos metadata failed这个问题之所以难查,很大程度上就是因为它的表象太单一、原因太分散,把经验沉淀下来,才是真正的解法。