如何排查Nacos线程数过多的问题

本文介绍如何排查Nacos线程数过多的问题。

问题现象

通过监控系统或其他手段,观察到应用的线程数过多,且大部分线程名中带有nacos等字样。

可能原因

  • 系统环境问题。程序读取到的CPU数量错误,导致线程池核心大小和最大大小过大。

  • 应用中创建过多Nacos-Client实例, 例如NacosNamingService或NacosConfigService。

  • 应用中存在错误使用方式,连续创建Nacos-Client实例,但新创建的Nacos-Client实例替换旧的Nacos-Client实例时未使用shutdown方法关闭线程池。

解决方案

本文以Java应用为例,其他语言应用可使用对应开发语言的相似命令执行。

  1. 首先确认是否创建过多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}'
    1. 如果Nacos-Client实例数存在过多,例如10个以上,则原因可能为应用中创建了过多Nacos-Client实例。请排查应用中使用Nacos-Client的代码,是否未复用Nacos-Client实例而是每次都创建不同的Nacos-Client实例。更多信息,请参见Dubbo框架在2.7.8版本中的Bug

  2. 如果Nacos-Client实例数量正常,请确认线程数是否符合预期。

    1. 使用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
    2. 如果上述线程数均超过了预期,则原因可能为连续创建Nacos-Client实例,但新创建的Nacos-Client实例替换旧的Nacos-Client实例时未使用shutdown方法关闭线程池,导致旧线程池未被关闭。更多信息,请参见Sentinel框架在旧版本中的Bug

    3. 如果上述线程数仅有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及以上版本生效。

    4. 如果上述线程均符合预期,说明线程池并没有泄漏,没有线程数过多的风险。如需降低线程数,可通过参数-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线程数过多的问题。