问题现象
某日中午 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 呢?欢迎评论区讨论!