Netcore内存分析及解决

问题来源

笔者从事供应链saas系统的开发工作,在系统线上环境运行后,根据线上性能监控及运维同事反馈,发现销售服务自启动后3天内就会出现内存占满及服务崩溃的现象。同时与运维在k8s上查看运行情况,了解到以下问题:

销售服务模块内存占满并崩溃
订单服务内存占有量太高
生产及基础服务的内存占有也比较高

以上现象已经极大的影响的系统使用,问题已经刻不容缓。

工具介绍

笔者之前调试dump文件的经验比较匮乏,一番学习后,学习到一点dump文件生成及调试知识。
生成dump文件的工具:

  1. coredump(适用于netcore3.0以下版本);
  2. dotnet-dump collect(适用于netcore3.0及以上版本)。

dump文件调试工具:

  1. lldb:linux下使用,且lldb的版本与netcore版本有对应关系;
  2. Dotnet-dump analyze 工具,windows与linux都可以使用,执行"dotnet tool install -g dotnet-dump"进行安装
  3. Windbg:windows下使用,下载安装
  4. VS:windows下使用,下载安装

dotnet-dump官方介绍,其中包含了dump文件收集及分析的具体介绍

因为笔者使用的是netcore2.2进行的开发,后续也将介绍如何使用lldb容器调试dump文件,同时此种方式也是使用步骤较为复杂的一种,调试所使用到的命令参考dotnet-dump官方介绍中的“分析 SOS 命令”部分。

环境搭建

1.首先搭建linux服务器(笔者使用的是centos),安装docker环境,服务器ip地址为192.168.100.102;
2. 100.102上使用" docker pull huangzhihong/dotnet-lldb:2.2.6"获取lldb3.9的容器;
3. 100.102上新建目录/tmp,使用rz命令将dump压缩包拷入,并使用tar zxf解压文件,稍后会将/tmp文件夹映射到docker容器中。
4. 使用命令"docker run -d -v /tmp:/dump -it --name lldb huangzhihong/dotnet-lldb:2.2.6 /bin/bash"运行容器
5. 执行命令docker exec -it 容器id /bin/bash进入容器,后续操作将在容器中进行
6. 安装对应版本的netcore sdk,要求与dump文件的运行版本一致,安装步骤如下:
curl -o sdk.tgz https://download.visualstudio.microsoft.com/download/pr/022d9abf-35f0-4fd5-8d1c-86056df76e89/477f1ebb70f314054129a9f51e9ec8ec/dotnet-sdk-2.2.207-linux-x64.tar.gz
mkdir -p $HOME/dotnet && tar zxf sdk.gz -C H O M E / d o t n e t e x p o r t D O T N E T R O O T = HOME/dotnet export DOTNET_ROOT= HOME/dotnetexportDOTNETROOT=HOME/dotnet
export PATH= P A T H : PATH: PATH:HOME/dotnet
7. 执行命令" lldb-3.9 dotnet -c /dump/dump文件 -o “plugin load /root/dotnet/shared/Microsoft.NETCore.App/版本号/libsosplugin.so”"
进入后执行"setclrpath /root/dotnet/shared/Microsoft.NETCore.App/2.2.8",到此dump调试环境搭建并启动成功,输入命令clrthreads,环境正确时将显示当前进程的线程情况。

问题分析

销售服务分析

根据现象来看,似乎是内存溢出的原因,导致容器崩溃,开始执行命令
Dumpheap -stat显示堆的统计信息,分析的dump包在运行时显示内存已占用1.5个g,使用此命令后显示其中有800M的空间处于free状态,而string和byte[]占用都处于百M以上。但仅从内存的角度来看,并不会造成容器崩溃。
执行eestack命令查看各线程堆栈跟踪,发现很多线程处于.Wait()状态,从网上的踩坑分享来看,很像同步调异步时,线程得不到释放的原因。此时开始修改framework的consumer中的事件响应处理方法,主要处理为EventingBasicConsumer改为AsyncEventingBasicConsumer。
同时根据线程堆栈来看,此处代码出现的线程只占其中一部分,更大一部分则为ManualResetEventSlim.Wait(),与上述解决的问题并不一致,蒙…。
此时根据运维同事反馈,在每次崩溃时,都会出现k8s健康检查时,销售服务无响应的情况, 看起来比较像容器内无可用资源(前段时间,另一个项目遇到一个问题,容器内进程正常运行,无异常,但运行一段时间后http请求无法连接上,分析后发现是http连接未释放造成资源问题)。执行命令clrthreads命令,发现线程总数650,但其中有620的前台线程,而程序目前业务量并不大且没有地方需要使用大量前台线程的功能。
分析holder.framework代码 ,并根据eestack中的跟踪堆栈来看,定位到分布式锁中有new thread的代码,同时分析分布式锁的源码发现问题原因。分布式锁内部使用了优化,且锁的用途并设计为锁的key值并不多。

订单服务分析(图片掉丢了,正在找)

订单服务只有为数不多的几个页面,且使用频率并不高,业务复杂度也不高,出现高内存原因大概率为溢出,而非数据缓存。
首页执行dumpheap -stat命令,出现string对象占用空间高达500M,且byte[]也占用200M+,如图
使用dumpheap -mt 00007f080c998a20,查看string的具体使用情况,发现有非常多的大字节对象,如图
使用dumpobj 00007f051cd26aa0显示对象内容,此时只有哦豁一声,不给显示值
没办法了,先查看byte[]是个什么情况。
执行命令dumpheap -mt 00007f080c9850f0,显示byte[]的具体内容。照样,非常多的大数组对象。
使用dumpobj 00007f051c2439a0 显示内容,从内容上看,很有点像splunk日志内容,再多试几个,基本都是类似内容,怀疑的矛头指向了NLog到splunk的推送,在该服务上没有清掉(很早就已经没有使用Nlog直接推送内容到splunk的方式)
不死心,bing上再搜索如何查看string,终于有了个方法,执行命令" memory read 00007f051cd26aa0 -f s -c 1024"得到结果如图所示。没办法将内容拼起来,很像httplog(内部开发的写http日志)中间件的内容。结合byte[]内容的分析得出,应该是Nlog往splunk推送消息,但推送失败,nlog就将内容放在内存中,并未释放。

问题处理

  1. 针对销售服务的问题,对分布式锁的代码进行了方法扩展,即支持长久锁业务(如获取businesscode),又支持临时锁业务(如发货单检查),详细查看RedisDistributedLockFactory.cs中的代码。同时将nlog.config中的splunk配置删除。
    最终进行问题复现,只需要模拟代码调用 CreateLockAsync方法创建分布式锁,测试环境模拟提交发货单即可。
  2. 针对订单服务,则将nlog.config文件中的splunk相关配置删除。
    问题复现,任意调用订单服务的post,put接口服务(笔者测试时只管数据格式正确,接口直接返回失败,能调用到httplog即可)。接口调用上千次进行测试复现。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值