本文介绍如何排查Nacos线程数过多的问题。
问题现象
通过监控系统或其他手段,观察到应用的线程数过多,且大部分线程名中带有nacos等字样。
可能原因
系统环境问题。程序读取到的CPU数量错误,导致线程池核心大小和最大大小过大。
应用中创建过多Nacos-Client实例, 例如NacosNamingService或NacosConfigService。
应用中存在错误使用方式,连续创建Nacos-Client实例,但新创建的Nacos-Client实例替换旧的Nacos-Client实例时未使用shutdown方法关闭线程池。
解决方案
本文以Java应用为例,其他语言应用可使用对应开发语言的相似命令执行。
首先确认是否创建过多Nacos-Client实例,使用
jmap -histo ${pid} > histo.log
命令,将应用中的内存实例对象记录在日志文件中,随后对日志文件进行过滤统计,并记录Nacos-Client的数量。# Nacos-Client注册中心客户端数量,正常情况下应该不超过3个。 grep "NacosNamingService" histo.log | awk '{print $2,$4}' # Nacos-Client配置中心客户端数量,正常情况下应该不超过3个。 grep "NacosConfigService" histo.log | awk '{print $2,$4}'
如果Nacos-Client实例数存在过多,例如10个以上,则原因可能为应用中创建了过多Nacos-Client实例。请排查应用中使用Nacos-Client的代码,是否未复用Nacos-Client实例而是每次都创建不同的Nacos-Client实例。更多信息,请参见Dubbo框架在2.7.8版本中的Bug。
如果Nacos-Client实例数量正常,请确认线程数是否符合预期。
使用
jstack ${pid} > jstack.log
命令,将当前线程信息打印到日志文件中,随后对日志文件进行过滤统计。# Nacos-Client所使用的线程池数量,其数量不应该超过Nacos-Client数量 * cpu数 * 8。 grep "nacos-grpc-client-executor" jstack.log | wc -l # Nacos-Client内部事件通知机制所使用的线程池,其总数不应超过5个 grep "nacos.publisher-" jstack.log | wc -l # Nacos-Client用于断线重连及发送pingpong心跳的线程,其总数不应超过Nacos-Client数量 * 2。 grep "com.alibaba.nacos.client.remote.worker" jstack.log | wc -l # Nacos-Client注册中心更新缓存所使用的线程池,其总数不应超过NacosNamingService数量 * (cpu数 / 2)。 grep "com.alibaba.nacos.client.naming.updater" jstack.log | wc -l # Nacos-Client注册中心接收UDP推送数据所使用的线程池,其总数不应超过NacosNamingService数量。 grep "com.alibaba.nacos.naming.push.receiver" jstack.log | wc -l # Nacos-Client注册中心在断线重连后,补偿非持久化服务数据的线程,其总数不应超过NacosNamingService数量。 grep "com.alibaba.nacos.client.naming.grpc.redo" jstack.log | wc -l # Nacos-Client配置中心监听配置所使用的线程池,其总数不应超过NacosConfigService数量。 grep "com.alibaba.nacos.client.Worker" jstack.log | grep -v "longPolling" | wc -l # Nacos-Client配置中心用于回调监听者Listener的线程池,其总数不应超过NacosConfigService数量 * 5。 grep "nacos.client.config.listener.task" jstack.log | wc -l # Nacos-Client配置中心监听长轮询所使用的线程池,其总数不应超过NacosConfigService数量 * cpu数量。 grep "com.alibaba.nacos.client.Worker.longPolling" jstack.log | wc -l
如果上述线程数均超过了预期,则原因可能为连续创建Nacos-Client实例,但新创建的Nacos-Client实例替换旧的Nacos-Client实例时未使用shutdown方法关闭线程池,导致旧线程池未被关闭。更多信息,请参见Sentinel框架在旧版本中的Bug。
如果上述线程数仅有nacos-grpc-client-executor、com.alibaba.nacos.client.naming.updater等与CPU数量有关的线程数超过预期,则原因可能为应用读取的当前节点的CPU数量不正确,该情况多出现于容器化的场景中。可通过调用Runtime.getRuntime().availableProcessors()查看应用读取到的CPU数量,确认读取数量过大时,可通过更换正确的环境进行修复,或使用参数
-Dnacos.common.processors
或环境变量NACOS_COMMON_PROCESSORS
进行强制指定。说明参数
-Dnacos.common.processors
和环境变量NACOS_COMMON_PROCESSORS
需要Nacos-Client版本为2.1.1及以上版本生效。如果上述线程均符合预期,说明线程池并没有泄漏,没有线程数过多的风险。如需降低线程数,可通过参数
-Dnacos.remote.client.grpc.pool.core.size
和-Dnacos.remote.client.grpc.pool.max.size
来设置nacos-grpc-client-executor线程池的数量。说明参数
-Dnacos.remote.client.grpc.pool.core.size
和-Dnacos.remote.client.grpc.pool.max.size
需要Nacos-Client版本为2.1.1及以上版本生效。nacos-grpc-client-executor线程池存在回收机制,客户端在长时间无请求时,会自动回收多余线程至最小值(默认为CPU数量 * 2)。在大量请求时,会自动扩充线程数至最大值(默认为CPU数量 * 8)。因此部分监控系统中会看到该线程名的线程ID会呈现增长趋势,该现象为正常现象,并非Nacos线程数过多的问题。