线上 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 呢?欢迎评论区讨论!

相关推荐
浩宇软件开发3 分钟前
Android开发,实现一个简约又好看的登录页
android·java·android studio·android开发
brzhang3 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
shepherd1114 分钟前
Kafka生产环境实战经验深度总结,让你少走弯路
后端·面试·kafka
南客先生10 分钟前
多级缓存架构设计与实践经验
java·面试·多级缓存·缓存架构
anqi2712 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
袋鱼不重17 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
m0_7401546719 分钟前
maven相关概念深入介绍
java·maven
嘻嘻哈哈开森19 分钟前
Agent 系统技术分享
后端
用户40993225021220 分钟前
异步IO与Tortoise-ORM的数据库
后端·ai编程·trae
会有猫25 分钟前
LabelStudio使用阿里云OSS教程
后端