前言

这周系统更新了一个版本,部署到线上.

客户反馈整个系统全部都卡顿,随即我们上服务器检查

发现整个服务器内存竟然达到了20-30G的占用..如图:

其中有一个订单服务,独自占用13-18G内存,

当它重启以后,内存会降低下来一段时间,但过不了多久 就又会增长上去

高度怀疑出现了内存溢出的情况,由于是线上服务器而且是离线内网.

项目又都运行在docker容器中,容器为了最小化,采用了极简的系统,几乎任何常见命令都没有.

所以考虑采用挂载额外辅助容器的形式进行调试.

正文

1.创建调试用的辅助容器

这个简单,我们直接创建DockerFile如下:

# 使用 .NET 5 SDK 作为基础镜像
FROM mcr.microsoft.com/dotnet/sdk:5.0 # 安装 dotnet-dump 、 dotnet-stack 、dotnet-counters、dotnet-trace的旧版本(兼容 .NET 5)
RUN dotnet tool install -g dotnet-dump --version 5.0.220101 \
&& dotnet tool install -g dotnet-stack --version 1.0.130701 \
&& dotnet tool install -g dotnet-counters --version 5.0.251802 \
&& dotnet tool install -g dotnet-trace --version 5.0.251802 \
&& apt-get update \
&& apt-get install -y unzip procps \
&& echo 'export PATH="$PATH:/root/.dotnet/tools"' >> /root/.bashrc #设定全局工具环境变量
ENV PATH="${PATH}:/root/.dotnet/tools" # 默认启动 bash 以方便交互
CMD ["/bin/bash"]

这里我们使用当前系统RunTime对应版本的SDK作为基础镜像.

然后安装我们调试程序信息需要的工具:dotnet-dump 、 dotnet-stack 、dotnet-counters、dotnet-trace

先介绍一下这些工具.

dotnet-dump:可以收集和分析 Windows、Linux 和 macOS dump的信息,可以运行 SOS 命令来分析崩溃和垃圾回收器 (GC)。

dotnet-stack:可以收集 .NET 进程中的所有线程捕获和打印托管堆栈。

dotnet-counters:是一个性能监视工具,可以临时监视.NET程序的运行状况和做一些初级的性能调查

dotnet-trace:在不使用本机探查器的情况下对正在运行的.NET Core 进程进行跟踪

2.将辅助调试容器附加到应用容器运行

首先我们需要重新run一个应用容器,并给它特权模式和系统级的调试权限,大概命令如下:

docker run -d --name myapp --privileged=true --cap-add=SYS_PTRACE -e ASPNETCORE_ENVIRONMENT=dev -e COMPlus_EnableDiagnostics=1 --volume /home/tmp:/tmp order-test:5.0

重点是privileged参数和cap-add参数,还有应用系统的tmp文件夹需要映射到宿主机

然后我们运行我们的调试容器并附加到应用容器

docker run -it --rm  --cap-add=SYS_PTRACE --pid=container:myapp --privileged=true --volume /home/tmp:/tmp dotnet-debug-tools:5.0

同样,它也需要privileged参数和cap-add参数,tmp临时文件也需要映射在宿主机和应用容器同样的目录下

注意--pid=container:myapp 中的myapp 是上面应用容器的名称.

然后,我们在调试容器直接运行命令:

ps aux

应该就能看到应用服务中的dotnet的进程了.因为在容器中运行,所以一般dotnet的PID是1,如图:

3.分析应用容器内dotnet进程的情况.

我们可以先使用dotnet-counters进行监控.

命令如下,其中-p是dotnet的进程编号:

dotnet-counters monitor -p 1

能得到如下结果:

% Time in GC since last GC (%)                                 0
Allocation Rate (B / 2 sec) 98,016
CPU Usage (%) 0
Exception Count (Count / 2 sec) 0
GC Fragmentation (%) 8.189
GC Heap Size (MB) 11,419
Gen 0 GC Count (Count / 2 sec) 0
Gen 0 Size (B) 192
Gen 1 GC Count (Count / 2 sec) 0
Gen 1 Size (B) 36,742,336
Gen 2 GC Count (Count / 2 sec) 0
Gen 2 Size (B) 8.8066e+09
IL Bytes Jitted (B) 7,248,623
LOH Size (B) 3.3414e+09
Monitor Lock Contention Count (Count / 2 sec) 0
Number of Active Timers 209
Number of Assemblies Loaded 426
Number of Methods Jitted 135,063
POH (Pinned Object Heap) Size (B) 1,299,832
ThreadPool Completed Work Item Count (Count / 2 sec) 0
ThreadPool Queue Length 0
ThreadPool Thread Count 12
Working Set (MB) 14,910

我们可以直接借助AI分析这个性能指标如下:

指标 当前值 分析建议
CPU Usage (%) 0 CPU 几乎未被使用,说明当前应用 处于空闲或阻塞状态。如果此时预期它应该在处理请求,说明可能卡在 I/O、锁、数据库等等待资源上。
GC Heap Size (MB) 11,419 MB 堆内存非常大(约 11GB),说明分配对象多或存在内存泄漏风险。建议配合 dump 分析对象分布。
GC Fragmentation (%) 8.189% 碎片率较低,尚可接受。通常低于 20% 问题不大。
LOH Size (B) ~3.34 GB Large Object Heap(LOH)占用了非常大的空间。意味着有很多大对象(如数组、字符串、缓存等)未被及时回收,或持续增长。需要进一步 dump 分析。
Gen 2 Size (B) ~8.8 GB Gen2 表示长时间存活对象。此值非常高,说明内存中存在大量老对象未被释放,极可能存在内存泄漏
Gen 0/1 GC Count 0 说明当前没有发生 GC,或者是刚启动。这种情况下内存持续增长会导致最终触发 GC 或 OOM。
% Time in GC since last GC 0% 目前没有 GC 时间消耗,结合上面 GC Count 是 0,一致。
Working Set (MB) 14,910 MB 进程实际占用物理内存约 14GB,结合堆大小与 LOH/Gen2 数值合理,但这也是很大的内存占用,需要关注增长趋势
Allocation Rate 98 KB / 2 sec 内存分配速率很低(接近空闲),说明当前没有明显的内存增长。
ThreadPool Thread Count 12 线程池线程数正常范围,无需担心。
ThreadPool Queue Length 0 没有待处理任务,说明任务执行速度没有瓶颈。
Exception Count 0 无异常抛出,良好。
Monitor Lock Contention Count 0 没有锁争用,说明线程间竞争不激烈。

根据AI的分析报告,我们可以得知:

GC Heap Size (MB) 堆内存极大达到了11G

LOH Size (B)大对象指标也很大,有3G的大对象

Gen2 长期活动的对象很多,占用了8G

这样我们基本就可以断定是在应用中出现了内存泄漏的情况.

也不用额外在看别的信息了. 我们直接使用dotnet-dump 抓取内存快照进行分析,抓取命令如下:

dotnet-dump collect -p 1

我们会得到如下结果:

Writing minidump with heap to ./core_20250514_030614
Complete

由于内存快照比较大,复制回来分析..难度比较高.我们可以直接继续利用dotnet-dump analyze 在线上分析

执行命令如下:

dotnet-dump analyze core_20250514_030614

然后我们就可以使用sos命令进行分析了.

既然是内存泄漏,我们直接查看托管堆里面的到底是啥情况,命令如下:

dumpheap -stat

正常是按从小到大排序的..所以很尬,我们划到最下面,看到如下结果:

由于我比较清楚这个代码的情况,直接发现了一个很明显的问题,

Order.OperApply.ApplyVoucherDetail  只是一个业务实体而已,但是在堆里面有29W个对象,

明显是很不合理的.

我们直接分析它.可以使用dumpheap -mt 获取它的所有实列地址.

dumpheap -mt 00007f117f67e590

可以得到如下结果:

我们选择最后一个Adderss寻找对象的根方法.命令如下:

gcroot 00007f0ca9b55818

可以看到如下代码内容(由于太多,我只贴出来有用的):

->  00007F0E643FB770 RabbitMQ.Client.Impl.AsyncConsumerWorkService
-> 00007F0DA4D0BBB8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StockManage.Handler.ProductInOutStockStatDistributedHandler+<HandleEventAsync>d__6, StockManage.Application]]
-> 00007F0AFCCE9020 System.Collections.Generic.Dictionary`2+Entry[[System.Object, System.Private.CoreLib],[Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, Microsoft.EntityFrameworkCore]][]
-> 00007F1035A778A0 Order.OperApply.ApplyVoucher

这四条,我们就可以看出来,是RabbitMQ的消息队列,在ProductInOutStockStatDistributedHandler方法,进行消费的时候

会通过EF CORE 创建这个ApplyVoucher的实例,接下来,我们就需要查看这个ProductInOutStockStatDistributedHandler,到底做了什么.

4.排查代码

直接去查看这个方法的代码,发现竟然没有任何一处使用了ApplyVoucher实体.

所以,我们直接运行本地调试,发现在这个方法结束后会去查询ApplyVoucher表.

分析代码后,我们发现,由于我们使用的是ABP的框架,在方法结束后,会自动写入审计日志,最后才会结束整个调用.

遂调查审计日志模块,发现有小伙伴在审计日志中对ABP的AuditLogInfo对象进行了序列化操作.

查询ABP源码发现,AuditLogInfo的EntityChanges中竟然储存了Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry对象

而EntityEntry对象又包含了整个DBContext上下文..然而我们的DBContext 又开启了懒加载的功能

所以当它被序列化的时候...等于在序列化整个数据库..(这里省略一百个C...)

赶紧屏蔽这段代码,并更新到线上. 问题瞬间解决了..

5.深入求证.

解决问题后,还是比较好奇,有没有同样使用ABP的兄弟遇见相关问题,遂去查询abp源码仓库..

竟然发现在Extracting a Module as a Microservice的相关说明里.

看到了这一段...

而且ABP框架还特意创建了一个IAuditLogInfoToAuditLogConverter来转换AuditLogInfo对象..方便后面进行序列化和存储..

后记

这一次分析线上问题的过程,还是比较有参考性的,所以记录一下.也希望对各位兄弟们有帮助.觉得OK的可以点个推荐~~.3Q~

记一次ASP.NET CORE线上内存溢出问题与dotnet-dump的排查方法的更多相关文章

  1. 记一次asp.net core 线上崩溃解决总结

    1.首先要先准备好环境,安装lldb 工具 要安装3.9版本的,因为每个版本对应dnc版本不一样,3.9的支持2.2 版本,然后确定分析的机器里dnc 版本和线上的生产环境是否一致,自己安装比较费劲, ...

  2. ASP.NET Core文件上传IFormFile于Request.Body的羁绊

    前言 在上篇文章深入探究ASP.NET Core读取Request.Body的正确方式中我们探讨了很多人在日常开发中经常遇到的也是最基础的问题,那就是关于Request.Body的读取方式问题,看是简 ...

  3. C# 6 与 .NET Core 1.0 高级编程 - 40 ASP.NET Core(上)

    译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 40 章  ASP.NET Core(上)),不对的地方欢迎指出与交流. 章节出自<Professiona ...

  4. [转载]ASP.NET Core文件上传与下载(多种上传方式)

    ASP.NET Core文件上传与下载(多种上传方式)   前言 前段时间项目上线,实在太忙,最近终于开始可以研究研究ASP.NET Core了. 打算写个系列,但是还没想好目录,今天先来一篇,后面在 ...

  5. 放码来战!HMS Core线上Codelabs挑战赛正式开始

    亲爱的开发者,在1024程序员节即将到来之际,HMS Core准备了一场线上Codelabs挑战赛,现向你发出诚挚邀请,希望你能将新奇的想法和对产品的思考融入代码,用技术与世界对话. HMS Core ...

  6. 码上来战!探索“智”感生活,HMS Core线上Codelabs挑战赛第4期开始!

    HMS Core线上Codelabs挑战赛第4期正式开始!我们向所有实践力超强.创新力满满的开发者发出邀请,用你的超级"码"力,解锁更多应用价值! 生活里,我们被手机"秒 ...

  7. ASP.NET Core - 缓存之内存缓存(上)

    1. 缓存 缓存指的是在软件应用运行过程中,将一些数据生成副本直接进行存取,而不是从原始源(数据库,业务逻辑计算等)读取数据,减少生成内容所需的工作,从而显著提高应用的性能和可伸缩性,使用好缓存技术, ...

  8. ASP.NET Core - 缓存之内存缓存(下)

    话接上篇 [ASP.NET Core - 缓存之内存缓存(上)],所以这里的目录从 2.4 开始. 2.4 MemoryCacheEntryOptions MemoryCacheEntryOption ...

  9. 记一次asp.net core 在iis上运行抛出502.5错误

    asp.net core 在iis上运行抛出502.5异常的部分原因以及解决方案 环境说明 已安装 .net core runtime 2.1.401 已安装 .net core windows ho ...

  10. ASP.NET Core 文件上传

    前言 上篇博文介绍了怎么样在 asp.net core 使用 Redis 和 Protobuf 进行 Session缓存.本篇的是开发过程中使用的一个小功能,怎么做单文件和多文件上传. 如果你觉得对你 ...

随机推荐

  1. QT5笔记:34. 视口和窗口

    ![image-20220504160327597](QT5 使用.assets/image-20220504160327597.png) 例子: void Widget::paintEvent(QP ...

  2. manim边学边做--标准相机

    在Manim动画制作库中,Camera类是负责管理屏幕显示内容的核心类,其功能涵盖场景设置.对象渲染.坐标转换等多个关键方面. Camera类作为Manim中渲染流程的核心,在动画制作中主要作用包括: ...

  3. 有关C++程序设计基础的各种考题解答参考汇总

    早先年考研的主考科目正是[算法与数据结构],复习得还算可以.也在当时[百度知道]上回答了许多相关问题,现把他们一起汇总整理一下,供读者参考. [1] 原题目地址:https://zhidao.baid ...

  4. Flink学习(一) 行情介绍

    想进大厂,必须掌握 Flink 技术!!! 随着大数据时代的发展.海量数据的实时处理和多样业务的数据计算需求激增,传统的批处理方式和早期的流式处理框架也有自身的局限性,难以在延迟性.吞吐量.容错能力, ...

  5. 【论文随笔】多行为序列Transformer推荐(Multi-Behavior Sequential Transformer Recommender)

    前言 今天读的论文为一篇于2022年7月发表在第45届国际计算机学会信息检索会议(SIGIR '22)的论文,文章主要为推荐系统领域提供了一个新的视角,特别是在处理用户多行为序列数据方面,提出了一种有 ...

  6. My'Bug

    修改时未校验工作经历是否为空

  7. [tldr] 使用ip.sb检查自己所在局域网的公网IP

    使用ip a等一些命令行工具可以帮助我们检查自己的内网IP,但是,如何获取自己的在公网下的IP(即当前所在的局域网被分配的公网IP) 如果使用爬虫,这个IP也是很重要的.BAN IP就是这个IP ht ...

  8. nginx 简单实践:负载均衡【nginx 实践系列之四】

    〇.前言 本文为 nginx 简单实践系列文章之三,主要简单实践了负载均衡,仅供参考. 关于 Nginx 基础,以及安装和配置详解,可以参考博主过往文章: https://www.cnblogs.co ...

  9. EmlBuilder:一款超轻量级的EML格式电子邮件阅读和编辑工具

    EmlBuilder 是一款超轻量级的电子邮件阅读和编辑工具,针对EML格式的文件具有非常强大的解析和容错能力,可实现超文本邮件的编写,并具备内嵌图片的编辑功能.该工具内部使用EmlParse对电子邮 ...

  10. linux(centos)配置ipv6网卡

    1.ipv6网卡配置文件和ipv4在同一个网卡配置文件中 vim /etc/sysconfig/network-scripts/ifcfg-eth0 设置好之后重启网卡生效 2.测试