线上 nacos 挂了 !cp 模式下,naming server down 掉问题深度解析!

问题现象

某日中午 12 点 40,生产环境很多服务同时收到大量 NacosException 告警。

异常详情如下,看详情,是 nacos naming 挂了,原因是跟 raft 相关。

查看监控,此时 cpu 使用率、内存、gc 都正常 ,nacos cofing 模块也正常。

中午 12 点 40 时,其他部门 golang 应用进行了发版,接入了 nacos,同时 ephemeral 设置的为 false。

ephemeral 是控制 nacos 使用 ap 模式还是 cp 模式的,我们 java 服务默认值为 true,也就是使用 ap 模式,一致性协议用的 distro;cp 模式一致性协议用的 raft,所以可以初步判断应该是这个上线造成的。

因为 nacos 不管是对配置文件、还是所注册服务的实例列表在本地都是有缓存的,所以此时虽然 nacos naming 挂了,但是并没影响到业务正常运行。

随后紧急取消了 golang 服务发布,重启了 nacos 集群恢复了正常。

原因排查

查看 nacos server error 日志,发现有这样一个异常。

查看源码,代码如下:

Op 取值只有这三个,这说明 request 中的 operation 字段的值并不是这三个之一。

继续查看 nacos 状态机的处理函数,可以看到 request 对象是通过 ProtoMessageUtil.parse 解析而来。

ProtoMessageUtil.parse 代码如下,先是尝试将字节流解析为 WriteRequest,如果解析不了,再尝试解析为 ReadRequest。

那会不会一个 read 的字节流被 parse 成 write 了呢?

试了一下,还真的可以,所以上述那个异常就可以解释通了,read 请求被解析成了 write 请求,拿到的 operation 为空字符串,当然就 IllegalArgumentException 异常了。

所以这是 nacos2.1.0 以下版本的一个 bug,在 2.1.0 修复了,我们使用的 nacos 是基于 2.0.4 构建的。

同时在 2.2.3 版本对此处异常进行了捕获。

这个状态机异常为什么会导致 nacos naming 挂掉呢?

nacos naming server 模块,每个节点都有个状态,当状态机抛异常后,server 就会处于 DOWN 状态。

同时 naming 模块有个过滤器 TrafficReviseFilter,会对所以的入口流量拦截,如果 server status != UP,就会直接以 503 返回所有请求。

所以我们服务请求 nacos naming 接口时都会返回 server is DOWNnow, detailed error message: Optional[The raft peer is in error: null],这个异常。

至于具体是如何设置 server status 为 DOWN 的呢,当 nacos 状态机异常后,会走到下述代码:

将 error msg 设置到 BasePersistentServiceProcessor 中。

有个定时任务会每隔 5s 检查下,如果一致性服务有 error,就会将改 server 状态设置为 DOWN。

当时的现象是 naming server node2 流量翻了近 2 倍,node0、node1 流量降到了正常时的 1/4,同时 nacos 控制台服务刷新服务列表有时有数据,有时没数据。

node2 是 leader,node0、node1 是 follower,证明 leader 并没有挂,两个 follower 挂了。

同时在日志里也没看到 node2 报的异常。

那为什么 node2 没挂呢?仔细看了下代码,leader 会走到第一步处理中,不会用到 ProtoMessageUtil.parse 做去反序列化,所以也就不会将 read 请求转为 write 请求,也就不会执行异常导致节点挂掉了。

至于为什么是 golang 服务上线导致的呢?

之前 nacos 上注册的节点都是临时节点,也就是使用的 ap 模式,没有用到 raft 协议相关,golang 服务使用的是 cp 模式,所以会使用 raft 协议相关功能。

实例注册的时候会触发到从 nacos 集群获取数据,此时会构造 read 请求,触发上述异常,导致节点挂掉。

总结

开源软件在为项目开发带来高效便捷的同时,也暗藏潜在风险。

许多开源软件都存在未被触发的 Bug,这些隐藏的 Bug 就像 "定时炸弹",在特定场景下才会暴露。

日常开发中,开发者可能对这些潜在问题毫无察觉,一旦触发,往往会导致严重的生产故障,带来巨大的业务损失。

因此,定期关注项目中使用的开源软件发版记录尤为重要。仔细查看每次版本更新说明,会发现其中包含大量的 Bugfix。

你工作中遇到过哪些开源软件的 Bug 呢?欢迎评论区讨论!

相关推荐
zybsjn3 分钟前
后端项目中静态文案国际化语言包构建选型
java·后端·c#
L2ncE12 分钟前
ES101系列07 | 分布式系统和分页
java·后端·elasticsearch
枣伊吕波29 分钟前
第十二节:第三部分:集合框架:List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
java·jvm·list
贺函不是涵33 分钟前
【沉浸式求职学习day51】【发送邮件】【javaweb结尾】
java·学习
无限大638 分钟前
《计算机“十万个为什么”》之前端与后端
前端·后端·程序员
初次见面我叫泰隆39 分钟前
Golang——2、基本数据类型和运算符
开发语言·后端·golang
forever_Mamba1 小时前
React - Fiber双缓冲
react.js·架构
南风lof1 小时前
ReentrantLock与AbstractQueuedSynchronizer源码解析,一文读懂底层原理
后端
你不是我我1 小时前
【Java开发日记】基于 Spring Cloud 的微服务架构分析
java·开发语言
写bug写bug2 小时前
彻底搞懂 RSocket 协议
java·后端