.NET 服务大量线程阻塞排查


前言

近来系统从 net framework472 升级到 net8,asp net core mvc 服务部署上线后,发现运行着就不响应了。


初步观察与分析

现象一

用户访问访问着,突然就不能响应了,用户 WEB 界面一直加载不出。
为排除链路上其他中间层的影响,立即尝试直接通过 IP+端口 的形式访问,发现确实没响应。
此时能确定就是服务的问题了。

现象二

通过把 nginx 流量全切走之后,服务没有快速恢复,而是过了有半个小时才恢复响应。
通过查看系统打印的日志,发现中间半个小时完全没打日志,有的半个小时前打的请求日志,半个小时后才打的响应日志。
那就说明中间有什么东西在等待堵塞了。

期间,CPU 并没有打满,还剩余挺多的,所以排除CPU资源不够导致响应处理不来了。
同时,发现进程的线程数量涨到好几百,这种现象很符合的情况是,
有什么资源同步阻塞导致线程被持有,而且这个资源肯定是频繁被使用到的,
才会导致线程一下子都用完了,并且需要不断分配新的线程,
新分配的线程还要很快就被抢掉了占用又堵塞了,如此反复,导致了这样的现象。


乍一看代码也没发现问题,那就祭出

dotnet-stack 诊断工具 – .NET CLI – .NET | Microsoft Learn

这玩意可以跟踪托管堆栈,大白话就是能看线程运行到哪了,在卡的时候一执行,那不就一眼就看到都卡在哪些地方了,灰常银杏。

上图感受下:


准备

下载,两种方式挑一种,我用的是直接下载。


打印报告

如图

# 先找出net的进程ID
dotnet-stack ps

# 打印当前托管堆栈
# 还有很多参数,有需要可以自己看官方文档
dotnet-stack report --process-id 8624

# 控制台就会打印出来了,当然也可以把控制台输出到文件里,方便文本编辑器查看

我这里是先再让用户访问,还原出卡的时候,然后立刻打印了几次报告进行分析的。


分析

这里的内容,以 Thread (线程标识) 为开头,到下一个Thread结束,作为一组数据。

看一下线程都在执行哪个函数,就能知道线程堵在哪了。

我发现有很多线程都卡在了同一个方法,是 log4net 的打印日志,
我们用到文本日志和MongoDB日志,先把MongoDB的关掉,发现就正常了。

进一步分析,原来是MongoDB日志是我们自己定义的打印器, MongoDB的client、database、collection 每次打印都会新建,

但是MongoDB官方说了这个是线程安全可重用的,所以就把这里改成单例了,
问题也随之解决了。


结论

如果发现线程数飙升,需要分析线程都堵塞在哪里了,可以使用 dotnet-stack。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注