ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Nacos集群 publish nacos metadata failed 排查

Nacos集群 publish nacos metadata failed 排查 1. 先给报错做归因这个 metadata 到底是谁的 metadatapublish nacos metadata failed这行日志我第一次遇到是在一个三节点的 Nacos 集群上。控制台打得开配置读得到业务服务注册也正常唯独nacos.log里像上了闹钟一样每隔一段时间就滚出一条 ERROR后面跟着一长串 gRPC 的异常堆栈。当时第一反应是注册中心要出大事连夜查下去才发现这个报错的性质差别极大有的是集群滚动重启瞬间的正常噪声有的则是集群已经半边瘫痪的信号。这篇就把这行日志从源码位置、端口模型、六类根因到复现实验一次讲透不管你是刚在 Windows 上解压完 Nacos 的新手还是在 Rancher、K8s 上维护多节点集群的老运维都能照着走一遍。1.1 同名的三种 metadata搜之前先看清组件名搜索引擎里敲 metadata failed出来的结果能把你带偏十万八千里。我见过有人为了 Nacos 的报错去翻model metadata for xxx not found. defaulting to fallback metadata这种模型加载日志也见过有人拿着Errors during downloading metadata for repository或者preparing metadata卡住的日志来问是不是 Nacos 挂了——前者是某个推理框架找不到模型描述文件后者是包管理器dnf/yum、conda、pip在拉取软件源索引跟注册中心一毛钱关系都没有。判断的方法特别土但极其有效看报错前面那一段的组件前缀和日志文件路径。Nacos 自己的日志全都在logs/目录下主日志是nacos.log启动输出在start.out注册相关的还有naming-server.log配置相关的有config-server.log。如果你的报错出现在npm install的输出里、在pip的进度条旁边、在 IDE 的索引阶段那压根不用往下看了直接去查对应的包管理器或模型加载器。只有当你确认这行字符串出现在logs/nacos.log、logs/start.out或某个 Java 进程的catalina.out里才值得往下读。1.2 服务端集群同步这行日志的完整上下文先把结论说清楚publish nacos metadata failed这条日志的主体绝大多数情况下是 Nacos 服务端自己打的不是你的业务应用打的。它是一个异步 RPC 回调里的异常打印位置在nacos-core模块的集群通信代理类里类名大意是ClusterRpcClientProxy方法名就是publishMetadata。这个方法干的事情很直白把当前节点的元数据封装成一个MetadataRequest通过服务端之间的 gRPC 通道发给集群里的其他节点其他节点收到后在本地更新我认识的这个伙伴现在是什么状态、什么版本。因为底层的 gRPC 调用是异步的失败不会当场抛异常打断主流程只会在回调的onException分支里打一行 ERROR 并附上堆栈。这也解释了为什么很多人第一次看到它时会困惑——程序明明没崩日志却在报错。同源的还有一位兄弟方法叫syncMetadata负责主动去拉取别的节点的元数据报错文案很接近排查思路完全一样。提示不用信我说的类名。自己动手验证最靠谱——把对应版本的源码拉下来在nacos-core目录里全局搜 publish nacos metadata failed 这个字符串十分钟就能定位到确切的方法和调用链。这个习惯比记答案有用得多因为 Nacos 的版本迭代很快类名和包路径是会被重构的。1.3 五分钟分诊它是噪声还是真故障这一步是全文最省时间的部分。同样是这行报错集群滚动重启时的零星几条和每隔几秒稳定刷一条完全是两个性质的问题。滚动重启场景下A 节点先起来会立刻尝试把元数据推给还没起来的 B、C 节点对方进程都不在连接被拒日志里必然出现这条 ERROR。等三个节点全部起来并且互相同步一轮之后这条日志会自己消失。我给自己的判断标准是这样的观察维度属于正常噪声属于真实故障出现频率启动后几十秒内几条之后消失每隔几秒到几十秒稳定重复长期不消失节点状态/nacos/v1/core/cluster/nodes里全部为 UP长期存在 SUSPICIOUS 或 DOWN 的节点业务影响注册、配置读写、动态刷新全部正常配置推送延迟、实例列表不全、部分客户端连不上堆栈根因UNAVAILABLE: Connection refused为主DEADLINE_EXCEEDED、PERMISSION_DENIED、UNIMPLEMENTED等那个/nacos/v1/core/cluster/nodes接口是排查集群问题的第一入口直接curl一下就能看到所有节点和它们的状态。需要注意的是如果开了鉴权这个接口也要带上认证信息否则拿不到结果别误判成接口不存在。把这条接口的输出和日志的堆栈结合起来看五分钟之内你就能决定是去睡个回笼觉还是打开防火墙的配置页面。2. 拆开看Nacos 集群元数据同步的链路与端口模型搞清楚这条日志是怎么产生的比背下十条解决方案更有价值。因为换个环境、换个版本症状会变形但链路是固定的。这一节把 Member 数据结构、一次完整的发布链路以及为什么 Nacos 2.x 之后这个报错突然变多讲清楚。2.1 Member 里到底装了什么值得每个节点互相通告Nacos 集群里每个节点在别人眼里都是一个Member对象核心字段包括 IP、端口、节点状态UP / SUSPICIOUS / DOWN、以及一大坨扩展信息。真正被发布出去的就是这些扩展信息典型内容包括节点版本号用来判断能不能用新协议通信、对应的通信端口比如 raft 端口、最近一次被刷新的时间戳、以及一些能力标记。为什么非要互相通告因为 Nacos 2.x 之后集群内部不只是转发请求还要做协议协商和故障转移。举个例子当一个节点要把请求转给另一个节点时它得知道对方是不是支持某个新特性否则直接发过去会收到UNIMPLEMENTED。再比如判断一个节点是不是活着靠的不只是 TCP 通不通还要看它的元数据是不是在持续刷新。所以你看到publish nacos metadata failed本质上是我联系不上这个伙伴或者联系上了但它不认我发的东西。2.2 一次 publishMetadata 的完整链路把链路拆成六步每一步都对应一类可能的故障这样排查的时候就有地图了发起节点构造MetadataRequest填入元数据类型和键值对从本地维护的 Member 表里取出目标节点的地址集群 gRPC 客户端复用到目标节点的连接没有就新建请求通过集群专用端口打到目标节点的 gRPC 服务端目标节点的处理器解析请求更新自己本地维护的成员信息回调通知发起方成功失败则走onException分支打印那行日志。第 3、4 步是出问题最多的地方。跨机房、跨网段、容器网络、安全组任何一层拦住了集群专用端口都会在第 4 步断掉。第 5 步出问题的典型表现是版本不一致导致反序列化失败日志里的堆栈会明显不一样。2.3 从 1.x 到 2.x 的端口模型变化是这行日志变多的头号原因Nacos 1.x 时代的端口结构非常简单一个主端口 8848 提供 HTTP控制台 OpenAPI再有一个 7848 用于 raft 一致性。很多老运维的防火墙白名单、云安全组、Docker 端口映射、K8s Service 定义都是在那时候定下的之后再没动过。Nacos 2.x 引入 gRPC 长连接之后端口模型变成了四个端口相对主端口的偏移用途不开放的后果8848主端口HTTP 控制台与 OpenAPI控制台打不开什么都别谈98481000客户端 gRPC应用注册失败日志出现连接检查失败98491001服务端之间集群 gRPC就是你正在看的这行publish nacos metadata failed7848-1000raft 一致性通信集群状态不一致CP 场景出问题注意 9849 这个端口它只用于节点之间通信客户端根本不碰。所以经常出现一种特别迷惑的现象业务应用全都正常注册、配置也能读到唯独服务端日志里刷这条 ERROR——因为客户端走的是 9848而节点之间走的 9849前者开了后者没开。如果你在云上只放行了 8848 9848这个坑你一定会踩。2.4 顺手回答一个面试常问的点集群成员信息是怎么同步的这个问题在面试里出现的频率不低。简短版本成员元数据走节点之间的点对点 gRPC 互推发布 主动同步双通道而服务实例数据走的是分片 异步广播的另一套机制两者不要混为一谈。面试官如果继续追问客户端连的是哪个端口正确答案是客户端 gRPC 端口也就是主端口 1000跟集群内部通信完全是两条路。把这个区分讲清楚基本就能把话题引到你想展开的方向上去了。3. 六类根因逐个拆解与修复有了前面的链路地图接下来就可以按可能性从高到低逐个排。我按实战中遇到的频次排序前两类能覆盖八成以上的案例。3.1 网络与端口先用三条命令证伪排查永远从最便宜的动作开始。在任意一个节点上执行下面三条基本就能定性# 1. 看本机四个端口是不是都在监听 ss -lntp | grep -E 8848|9848|9849|7848 # 2. 从节点 A 测到节点 B 的集群 gRPC 端口 timeout 3 bash -c cat /dev/null /dev/tcp/10.0.0.12/9849 echo 9849 OK || echo 9849 BLOCKED # 3. 拿到当前集群成员列表和状态 curl -s http://127.0.0.1:8848/nacos/v1/core/cluster/nodes | python3 -m json.tool第一条确认进程真的在监听而不是只监听了 127.0.0.1。第二条是重点/dev/tcp这个技巧在任何装了 bash 的机器上都能用不需要额外装 telnet 或 nc。第三条看成员状态。如果第二条不通接下来就是分层排查本机firewalldfirewall-cmd --add-port9849/tcp --permanent firewall-cmd --reload、Windows 自带的防火墙默认拦截入站这是 Windows 上单机折腾集群时最隐蔽的坑、云厂商的安全组、K8s 的 NetworkPolicy、以及容器编排层的端口放行。每一层都要单独确认不要假设上一层通了下一层就通。注意/dev/tcp只验证 TCP 三次握手能不能建立。如果握手成功但应用层拒绝问题就在鉴权或协议层别在这一步就下结论说网络没问题。3.2 节点发现配置cluster.conf 与模式开关网络通了节点之间也可能互相不认识。这时候看三样东西。第一样是conf/cluster.conf。集群模式下这个文件每一行是一个节点地址格式是IP:端口端口填主端口也就是 8848千万不要填 9848 或 9849填错了对方根本连不上。常见的低级错误包括写了127.0.0.1每个节点都以为集群只有自己、写了容器的内网 IP宿主机或别的容器访问不到、写了主机名但 DNS 解析不了、以及三台机器上的cluster.conf内容各不相同。第二样是启动模式。单机模式sh startup.sh -m standalone下不会走集群同步自然也不会出现这条日志一旦你以集群模式启动但cluster.conf是空的节点就会陷入我以为我是集群但没人告诉我伙伴是谁的状态。Windows 上有个经典陷阱直接双击startup.cmd会走集群模式需要手动改脚本里的模式变量或者在命令行带参数启动很多人就是这么莫名其妙多出一堆报错的。第三样是节点发现方式的开关。Nacos 2.x 的application.properties里有一个控制成员列表来源的配置项键名是nacos.core.member.lookup.type这一类它决定了集群成员是从本地文件读、还是从地址服务器拉。如果你的部署方式是配置文件 编排平台自动生成但开关配成了地址服务器模式成员列表就永远是空的或者过期的。这个键的取值以你版本里application.properties的注释为准改之前先备份。3.3 容器与编排环境Docker、Rancher、K8s 的经典坑容器化之后这个问题会换一副面孔出现。Docker 单机跑多节点时端口映射不全会导致宿主机侧的客户端连不上但容器之间通常还是通的同一个自定义网络里容器可以直接访问对方的容器端口。所以如果你在宿主机上执行docker run只映射了-p 8848:8848然后发现宿主机上的应用注册不上、但容器内部一切正常别怀疑人生把 9848 一起映射上。完整一点的做法是把四个端口都映射出去主机端口按884X / 984X / 984X1 / 784X的规律错开。K8s 和 Rancher 场景下最要命的是Pod IP 漂移。Nacos 把节点的 IP 写进了成员表Pod 一重建 IP 就变了老节点还在往已经不存在的 IP 发元数据日志就会持续报错。正确姿势是用 StatefulSet 配合 Headless Service让每个 Pod 有稳定的 DNS 名并把节点列表写成nacos-0.nacos-headless:8848这种形式而不是写 IP。Service 定义里也要把 9848、9849 都暴露出来只暴露 8848 的方案迟早出问题。至于部署在 Rancher 上怎么让外部访问我的一般建议是控制台8848和客户端 gRPC9848都要暴露集群内部端口9849、7848只在集群内网放通。暴露方式优先选四层的 NodePort 或 LoadBalancer因为 gRPC 走的是 HTTP/2 长连接用七层 Ingress 转发需要额外配置且容易踩坑。另外别忘了这套接口暴露到公网的安全问题后面第 6 节会专门说。3.4 版本与二次开发分支混合版本、国产库适配补丁集群里三个节点的 Nacos 版本必须严格一致这句话怎么强调都不过分。2.x 的不同小版本之间内部 RPC 的消息结构是有差异的混着跑就可能出现反序列化失败表现之一就是元数据发布失败堆栈里往往能看到UNIMPLEMENTED或者类找不到。二次开发的场景风险更高。比如为了适配国产数据库社区里常见的做法是实现数据源相关的 SPI 接口并把 JDBC 驱动打进包里。这类补丁有个硬要求所有节点必须使用同一份构建产物。如果只给部分节点换了包就会出现A 节点发过来的请求B 节点解析不了的情况日志表现和网络不通很像但网络测试全通特别容易把人带偏。遇到这种网络全通但就是报错的情况先比对三个节点的 jar 包哈希值和版本号比继续抓包省事得多。顺带提一句集群模式必须使用外部数据库单机模式默认的嵌入式数据库在多节点下会导致各节点数据不一致进而引发一连串看似无关的怪问题。这一类问题不在这条报错的直接因果链上但排查到后期经常要顺手确认一下。3.5 慢节点、GC 与超时配置都对但依然报错有一类情况特别气人端口全开、配置全对、版本一致日志还是偶尔报。这种通常是节点响应慢导致的请求超时堆栈里是DEADLINE_EXCEEDED。慢的原因通常有三个。一是节点所在机器 CPU 被打满或者频繁 Full GC元数据这种小请求都排不上队二是大量客户端频繁重连把 Netty 的工作线程占满了Nacos 2.x 的长连接底层就是 Netty连接数暴增时线程池会先扛不住三是虚拟机或容器被超卖CPU 时间片抢不到。处理这类问题的正确顺序是先治因再调参。先看机器的负载、GC 日志、连接数把慢的根因解决掉。只有在确认系统整体健康、只是偶尔抖动的情况下才考虑适度放宽集群 RPC 的超时参数。上来就调大超时时间本质上是把问题藏起来等到真正需要故障转移的时候可能会更糟。3.6 鉴权与 TLS开关不一致的隐性杀手开启鉴权之后集群内部的 RPC 也是要走认证的。如果只给部分节点开了鉴权或者三个节点配置的密钥不一致那么节点间的请求会被拒绝堆栈里会出现PERMISSION_DENIED日志一样是这个文案。所以鉴权开关和密钥必须全集群统一改的时候要三个节点一起改、一起重启。TLS 也是同理。如果只有一部分节点开启了加密通信握手阶段就会直接失败堆栈里通常能看到 SSL 相关的字眼。这类问题的特点是现象对称性很强——往往是单向失败A 能推到 BB 推不到 A看日志的分布规律就能猜到是配置不一致。4. 一次完整复现把错误稳定地造出来再修掉知道原理之后最好的巩固方式是亲手把错误造出来。下面这套流程我在测试环境里跑过好几遍稳定复现适合用来验证你的排查思路。4.1 搭一个最小三节点环境用 Docker 自定义网络起三个节点关键点是节点之间要能通过容器名互相解析docker network create nacos-net docker run -d --name nacos1 --network nacos-net --hostname nacos1 \ -p 8841:8848 -p 9841:9848 -p 9849:9849 -p 7841:7848 \ -e MODEcluster \ -e NACOS_SERVERSnacos1:8848 nacos2:8848 nacos3:8848 \ -e PREFER_HOST_MODEhostname \ nacos/nacos-server:v2.2.3节点 2、节点 3 照抄只改容器名、主机名和宿主机端口映射。注意NACOS_SERVERS里必须写容器名加 8848 主端口不能写 9849这是新手最容易搞错的一处。三个节点全部起来之后用前面的成员查询接口确认状态都是 UP基线环境就算搭好了。4.2 制造失败两种手法第一种手法最省事用来复现噪声把节点 3 停掉然后重启节点 1。节点 1 启动过程中会尝试把元数据推给节点 3对方进程不在日志立刻出现这条 ERROR。等节点 3 重新起来报错自动消失。这个实验能让你亲身体会到什么时候可以忽略它。第二种手法用来复现持续故障在节点 1 上屏蔽到节点 2 集群 gRPC 端口的出站流量模拟端口未放行。# 在节点 1 上屏蔽到节点 2 的 9849 iptables -I OUTPUT -p tcp -d 节点2的IP --dport 9849 -j DROP # 盯着日志看 tail -f logs/nacos.log | grep --line-buffered publish nacos metadata failed # 排查完记得删掉规则 iptables -D OUTPUT -p tcp -d 节点2的IP --dport 9849 -j DROP这时候你会发现报错开始规律性地刷屏而且节点 2 在集群成员列表里的状态会逐渐变差。删掉规则之后一般在下一轮同步周期内就恢复正常。这个实验的价值在于你能亲眼看到从网络中断到成员状态变差到日志报错的完整因果链以后再遇到类似问题看一眼日志频率就能大概猜到是哪一层。4.3 一份可以直接贴的巡检脚本我把常用的检查项写成了一个小脚本放在跳板机上每次集群出问题先跑一遍#!/bin/bash NODES10.0.0.11 10.0.0.12 10.0.0.13 PORTS8848 9848 9849 7848 for n in $NODES; do for p in $PORTS; do if timeout 2 bash -c cat /dev/null /dev/tcp/$n/$p 2/dev/null; then echo [OK] $n:$p else echo [FAIL] $n:$p fi done done echo ---- cluster members ---- curl -s http://10.0.0.11:8848/nacos/v1/core/cluster/nodes | python3 -m json.tool 2/dev/null echo ---- error count in last 200 lines ---- grep -c publish nacos metadata failed logs/nacos.log 2/dev/null || echo log not found here把它跑一遍输出结果基本就等价于一份体检报告了。哪些端口不通、哪些节点状态不对、报错量有多大一眼看完。5. 客户端侧的同名问题实例 metadata 发布失败前面讲的是服务端。但如果你是在业务应用的日志里看到类似的报错方向要拐个弯——那属于实例注册时携带的元数据没被服务端接受跟集群内部同步是两回事。5.1 注册链路里metadata 是怎么被带上去的以 Spring Cloud Alibaba 为例应用启动时会把spring.cloud.nacos.discovery.metadata下的键值对附加到实例信息里连同 IP、端口、权重、集群名一起注册到服务端。之后每一次心跳都会携带这份元数据。Dubbo 接入 Nacos 作为注册中心时也是同一套逻辑只是配置的前缀不一样。链路变长了出问题的点自然就多配置解析、序列化、网络传输、服务端校验任何一环不对劲都可能失败。这里要区分清楚——服务端侧失败会打publish nacos metadata failed客户端侧失败通常打的是连接类异常或服务端返回的错误码两者不要混。5.2 四个高频的写法错误第一个是元数据值类型不统一。YAML 里写gray: true解析出来是布尔值有的版本序列化时会出问题。稳妥做法是全部加引号写成字符串gray: true。第二个是把元数据当仓库用。我见过有人把整份灰度规则 JSON 塞进实例元数据里动辄几 KB。元数据会跟着每次心跳走体积一大就把心跳报文撑胖轻则延迟上升重则超出消息大小限制直接失败。经验值是单个实例的元数据控制在几百字节以内真要传复杂规则放到配置中心里元数据只留一个版本号或者规则标识。第三个是命名空间填错。控制台里看到的是命名空间的名称但配置文件里要填的是命名空间 ID。这两个东西长得像但不是一回事填错了会导致注册到默认命名空间或者根本注册不上而且报错信息很不直观。第四个是鉴权信息缺失。服务端开了鉴权之后客户端必须带上用户名密码否则注册请求会被直接拒掉。这类失败在服务端看是鉴权失败在客户端看是注册异常需要两头对着看。5.3 用 OpenAPI 做隔离验证绕开框架判断到底是框架配置的问题还是服务端的问题最快的办法是绕开框架直接调 OpenAPI 注册一个测试实例# 注册一个带 metadata 的实例metadata 需要 URL 编码 curl -X POST http://10.0.0.11:8848/nacos/v1/ns/instance \ --data-urlencode serviceNameblog-demo \ --data-urlencode ip10.0.0.99 \ --data-urlencode port8080 \ --data-urlencode namespaceIddev \ --data-urlencode metadata{version:v1} # 查回来确认元数据有没有落库 curl -s http://10.0.0.11:8848/nacos/v1/ns/instance/list?serviceNameblog-demonamespaceIddev \ | python3 -m json.tool如果这条 curl 能成功、查回来 metadata 也在说明服务端完全正常问题百分百在你的应用配置里如果这条 curl 都失败那就别在业务代码里找原因了先修服务端。5.4 顺带说清那个伴生报错用 Spring Cloud 2021 及以上版本时还有一个极易撞上的报错no spring.config.import property has been defined。它跟元数据没关系但经常和注册问题一起出现把人绕晕。原因是新版本的 Spring Cloud 要求显式声明配置导入来源。两种处理方式要么老实加上spring.config.import: optional:nacos:${spring.application.name}.yaml要么临时关掉这项检查spring.cloud.nacos.config.import-check.enabledfalse。我倾向于前者因为显式声明之后配置的加载顺序和来源清清楚楚排查问题的时候少一堆猜测。6. 速查表与踩坑记录6.1 常见问题速查表堆栈里的关键字大概率原因优先动作Connection refused / UNAVAILABLE9849 未放行或目标进程没起用/dev/tcp逐层测连通性DEADLINE_EXCEEDED节点负载高、GC 频繁、网络慢看机器负载与 GC 日志别急着调超时PERMISSION_DENIED鉴权开关或密钥不一致三个节点统一鉴权和密钥后一起重启UNIMPLEMENTED集群内版本不一致含二开补丁比对三节点 jar 包哈希与版本号SSL / handshake 相关TLS 只在一部分节点开启统一加密配置注册返回 4xx / 参数错误命名空间 ID 填错、元数据非法用 OpenAPI curl 隔离验证6.2 几个我实际踩过的坑踩得最惨的一次是在云上扩了一个节点加进了节点列表防火墙端口也放行了但日志一直刷这条报错。查了两小时才发现新节点的安全组规则加在了另一台机器上——云环境的规则是挂在实例维度上的不是挂在集群维度上的每个节点都要单独确认。从那以后我的习惯是新增节点后先在那个节点上跑一遍端口巡检脚本再跑一遍从旧节点到新节点的连通性测试两项都过才继续。第二个坑是关于cluster.conf的格式。有一版配置文件里我手滑写成了ip:9849因为当时脑子里全是 9849 这个端口号。结果是节点之间互相不认识日志报的正是这条错。集群配置文件里永远填主端口这个规则记牢。第三个坑发生在容器环境控制台里看到的节点 IP 全都是容器的内网地址从宿主机curl永远超时。后来改成用主机名模式才稳定下来。容器化部署时节点地址的可达性要从谁需要访问它这个角度去验证而不是从我自己能不能访问这个角度。6.3 最后几条实操建议如果你的集群要对外提供服务控制台和 OpenAPI 尽量别直接暴露在公网上。安全扫描工具会提示 Nacos 的部分接口存在未授权访问的风险标准做法是开启鉴权、修改默认密钥、把接口收敛到内网外部流量统一走网关转发。这件事跟性能无关但一旦出事就是大事值得花半小时配置。另外把这条日志加进你的监控规则里。做法很简单对logs/nacos.log里的这个关键字做计数设置一个阈值比如十分钟内超过 20 条就告警。因为它是间歇性的人工盯日志几乎盯不住但对机器来说只是个累加计数。我加了这个告警之后至少提前发现过两次节点间网络抖动的问题——那时候业务还完全正常等真出问题再查就晚了。至于单机玩票的场景比如在 macOS 或者 Windows 上解压一个包就想跑起来记住一件事就行单机模式下用带参数的命令启动sh startup.sh -m standalone或对应平台的脚本参数别用默认的集群模式。默认模式下没有集群配置启动起来会有一堆看上去很吓人的报错其实跟你没关系。
返回列表