线上问题排查系列 - unable to create new native thread

前言

大家好,我是明哥!

最近项目忙着上线,有段时间没有更新公众号了。

借这次费了老劲才解决的一个线上的问题,跟大家分享下排错经验,也聊聊IT感悟。

明哥一直觉得,IT领域各个技术之间都是相通的,是融合在一起来支撑企业的整个IT系统的,相互之间是没有明显边界甚至没有边界的。比如大数据和数据库之间,大数据和传统IT技术(JAVA EE)之间,大数据和云计算之间,大数据和AI之间,大数据和容器化(k8s, docker)之间,是互相促进,越来越紧密地融合在一起的;同时开发和运维之间的界限在模糊(DevOps理念),数据仓库和数据湖之间的界限在模糊(LakeHouse 数据湖仓,湖仓一体的概念),流处理和批处理之间的界限也在模糊(批流一体的趋势)。

所以作为IT从业人员,我们可以专注与某个技术方向,但不应该自我设限,只关注某个方向而限制了自己的发展。一个资深的IT架构师,既需要有技术的广度,也需要有技术的深度,还需要一颗关注技术发展趋势热爱学习持续学习的心,才能既做好技术选型和架构设计,同时又不沦为夸夸其谈,遇到问题能切切实实解决实际问题。正所谓它山之石可以攻玉,IT不分家,开发运维需要一把抓,就是这个道理。

问题现象

某业务系统发起流程对多个上游系统的数据并发进行采集和计算时,采集和计算任务都很容易失败,而该业务系统对多个上游业务系统分别进行串行的采集和计算则能够执行成功;在控制台和服务器后台日志,能够看到异常信息:java.lang.OutOfMemoryError: unable to create new native thread;

问题分析

该异常信息很容易误导人认为是内存原因,而武断地向客户提出需要增加内存。但查看jvm源码,可以知道该错误的可能原因有两个:JVM throws thisexception when JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR (memory exhausted ) or JVMTI_RESOURCE_EXHAUSTED_THREADS (Threads exhausted),即可能是内存原因也可能是线程数原因。

在我们这里,首先由于是创建native thread时报的oom,所以肯定不是 jvm 堆内存大小的问题;然后通过free – h 监控作业执行时的内存,可以发现异常发生时系统仍有大量内存,所以应该也不服务器内存的问题;

接下来自然会想到可能是创建的线程超过了操作系统的限制,可以通过查看/etc/security/limits.conf和/etc/security/limits.d/20-nproc.conf中的配置来获知用户的 soft 和hard nproc即线程数限制,然后可以通过 top 查看作业执行时系统实际的线程数(准确来讲,应该查看并计算hundsun用户实际的线程总数),我们确实发现用户实际的线程总数确实达到了soft nproc限制的 值了,所以报了上述错误。

注:也可以通过命令 ps -eo nlwp | tail -n +2 | awk '{ num_threads += $1 } END { print num_threads }' 查看实际的线程总数。

问题解决初次尝试

通过更改配置文件/etc/security/limits.conf和/etc/security/limits.d/20-nproc.conf,调整soft 和 hard nproc到65536, 然后通过ulimit –a 验证参数下参数确实生效了。

然后重新运行业务系统发起流程,上述问题任然存在。

 

问题进一步分析与解决(问题原因与细节)

其实上述分析思路是对的,确实是用户线程数的限制引起了上述问题;更改该参数的方法也是正确的(即一般不建议在/etc/profile, /etc/bashrc, ~/.bashrc, 或~/.bash_profile中直接通过ulimit –u xxx配置nproc);通过ulimit –a 验证当前生效的参数的值的思路也是对的。

但是忽视了一个细节,就是对于更改参数前已经存在的进程,以及这些已经存在的进程 fork出的新进程,其实际生效的nproc仍然是更改前的值,所以当这些更改参数前已经存在的进程及其子进程需要fork 新进程或新线程时,就会抛出上述java.lang.OutOfMemoryError:unable to create new native thread 异常!

而在我们的业务场景中,业务流程的发起是由调度工具定时发起或用户手动通过微服务触发的,该调度工具和微服务都是更改参数前已经存在的进程,所以实际生效的仍然是更改前的 nproc参数值!

可以通过 cat /proc/<pid>/limits 查看运行中的线程实际生效的参数的值。

另外还有个细节,我们这里没有碰到,但一并讲下,就是systemd 控制的service unit, 是不受 /etc/security/limits.conf 或etc/security/limits.d/20-nproc.conf中的配置参数影响的,对于他们需要在配置文件中配置LimitNOFILE(推荐配置/etc/systemd/system/<servicename>.d/override.conf,而不是/usr/lib/systemd/system/<servicename>.service,因为通过rpm等包管理器升级时后者会被覆盖掉)。

如上所述,在更改了/etc/security/limits.conf和/etc/security/limits.d/20-nproc.conf中配置的用户的 soft 和hard nproc 后,重启相关微服务和调度工具,问题最终解决了。

代码中异常处理规范建议

在排查该问题的过程中,查阅了相关代码,发现很多异常日志的打印不是很规范,为出现问题时的排查挖了很多坑。一个建议的异常处理规范是,遇到异常时,各个模块要在自己的日志中打印异常详细信息,同时也要向上抛出原始异常的信息。(如果遇到异常时没有在日志中打印异常细节,而仅仅是向上抛出了异常,是不利于事后通过日志排查问题的;代码捕获异常后可以抛出自定义的异常,但需要包含原始异常的细节,不然也也不便于问题排查)。

比如以下代码处理的就不太好:

欢迎大家关注同名微信公众号,后台留言加群交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明哥的IT随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值