记一次.NET内存居高不下排查解决与启示
前情
我们有个海外的项目,一共70个服务,前前后后花了超过一年时间完成了云服务迁移和架构调整。主要是架构调整了,原来的docker swarm托管服务,新架构改为Kubernetes托管。几台云服务器将n个服务堆在一起,只会对服务器资源做监控。新架构在新的云服务上线后,暴露了很多内存问题。在此拿某个服务根据解决过程,给个排查思路,希望能给到大家一点启示。
问题
服务为一个普通的ASP.NET Core gRPC工程,平常没什么流量。HPA设置的最大副本数为5,生产环境服务启动后,Pod内存达到或超过K8s内存请求值(512Mi),自动触发扩展到5个实例,即副本数达到最大。这与QA环境表现并不一样,也就没有在测试阶段发现。需要想办法复现、排查解决高副本和高内存占用问题。

部署里面对容器的资源,容器资源只做了对CPU和内存的预留设置(request),没有做上限设置(llimit)。从内存曲线上看,很多副本的内存甚至超过了请求的512Mi。不过有一点很奇怪,服务的接口并没有出现性能下降的迹象。

问题复现
尝试在QA环境对相关接口进行压测,问题能复现,表现为HPA副本扩展后各个POD的内存居高不下,维持在500~600Mi长时间不释放,有时候压测甚至能冲到800Mi。即使没有什么接口请求,也需要超过20个小时才缓慢下降到350Mi左右。但尝试本地VS诊断工具则并没有发现什么内存不释放问题,除了一些个大对象驻留问题。
代码与dotnet-dump
因为其他类似的服务并不会这样,所以第一时间怀疑是代码问题,但这么想是错的,下面交代。怀疑代码问题后,想着是不是有什么内存泄漏,找了个服务的接口在QA压测后问题能复现(即内存长时间不释放)。看了好几遍代码,除了一些个ToList用的太过频繁并没什么问题(也与内存问题不相关),用VS诊断工具检查内存有运行又没内存泄露问题。于是在QA环境用dotnet-dump把内存快照下载回来分析,找到了个大对象堆LOH驻留类型的类,而且VS诊断工具找到的类是同一个,接着定位到了接口调用这个类的地方。业务调用很单纯,这个类就从数据库用Dapper查出来得到列表,然后分组计算下数据,不会有什么内存泄露的机会;但注释掉此部分查询则内存不再上升到500~600Mi,只在300Mi左右,而且内存使用率下降也变快了。继续二分法+注释大法调试,最后只保留数据库查询语句而不做后续业务处理,连引用都不做,内存还是会达到500~600Mi。这就让人摸不着头脑了,代码肯定是相关的,数据库查询几下一列表数据都能让内存达到预留临界值(request),列表也才约11000条数据,虽然确实是LOH对象,但不至于造成这么严重的内存不释放现象。
GC调参
代码摸不着头脑,就想办法调试下GC。
方案一: 定时调用GC.Collect来回收垃圾
加入定时执行GC.Collect()后,内存占用能立即回落,这方案似乎也可以


方案二:GC配置调整:配置内存限制感知-DOTNET_GCHeapHardLimit
添加环境变量DOTNET_GCHeapHardLimit=0x20000000 # 512Mi的十六进制值,能限制内存的使用,但并不能让GC能敏感地进行回收,方案失败

方案三:切换为GC场景类型到工作站
原默认为Server GC,指定为Workstation GC后,内存占用不到180Mi,扩容缩容正常,这方案看起来也可以

调试结束,方案一和方案三似乎可行,查了相关资料后,两个方案其实都有问题。方案一是代码主动强制执行了垃圾回收,但大多数情况下并不不被建议在代码里面去执行,因为执行GC.Collect多多少少会影响服务性能,GC自己能感知内存使用率然后自动进行执行回收。至于方案三,不同的模式本来就对应着不同的服务场景,服务本身就是后端接口,切换为工作站模式也许可行,但ASP.NET Core默认就是Server GC,Server GC模式本身为了支持高性能并不会频繁执行垃圾回收(.NET 9开售则不同,.NET 9开始ASP.NET Core默认是第三种模式,.NET 8也支持这种模式,只不过不是默认的)。
为容器限定内存上线
查资料过程中才了解K8s的资源设置是有预留设置(request,又称请求设置)和上限设置(limit),服务只设置了请求request部分,没有limit部分,那有没有可能是服务容器因为没有被设置内存limit,导致GC如脱缰野马般豪气地使用内存呢?那为啥内存不释放?就是Server GC感觉内存还是够用的,具体文章参考:工作站和服务器垃圾回收和动态适应应用程序大小 (DATAS)。先查询下可用内存吧,于是加个下面接口查询:
app.MapGet("/runtime/memory", () =>
{
return GC.GetGCMemoryInfo().TotalAvailableMemoryBytes.ToString("N0");
});
结果返回:

可用内存居然高达4Gi,真相很接近了。接着为服务设置内存limit为512Mi,再次查询得到可用内存为512Mi。没错!就是少设置了内存上限,没有这个,此时可用内存为节点内存(4GB);加了limit重新压测,曲线:

事件如下:

程序内存释放正常!副本数释放也正常!另外接口响应时间没有受到影响,问题得到解决!
总结
服务内存曲线高居不下是因为容器没有被限制内存,K8s没有指定内存limit,可用内存就是节点/宿主机的物理内存,高达4GB。没有设置内存limit,但是设置了HPA,于是服务一启动经过一些时间内存超过HPA阈值造成副本数增加;GC默认是Server GC,其感知的内存足够所以不释放(包括小对象和大对象)。虽然主动调用GC.Collect则可以释放,但一般不会这样做,因为GC有自己的一套逻辑。限定内存为0.5Gi后,内存释放曲线正常,HPA扩缩正常,响应时间正常,问题得到解决
启示
如果遇到类似内存居高不下问题,先确定.NET版本极其GC是Server GC还是Workstation GC。然后再确定其分配的可用内存是多少,K8s下要检查其资源limit有没有被设置。如果被设置之后依然有内存不释放/泄露问题,再怀疑代码问题。
参考:
记一次.NET内存居高不下排查解决与启示的更多相关文章
- 记一次java内存溢出的解决过程
注:本文主要记录这次解决内存溢出问题的过程而不是具体问题. 最近在写一个搜索引擎,使用倒排索引结构进行文档检索,保存索引的基本思想是先将倒排列表保存到内存中一个有序Map里(TreeMap),然后当内 ...
- 解决WebBrowser控件会导致应用程序占用内存居高不下问题
WebBrowser 是一个 .NET 控件类,在 .NET Framework 2.0 版中新增.WebBrowser 类使用户可以在窗体中导航网页,下面我们来看在开发中碰到WebBrowser控件 ...
- java内存溢出的解决思路
原文地址:https://www.cnblogs.com/200911/p/3965108.html 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能 ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
- Redis性能问题排查解决手册(七)
阅读目录: 性能相关的数据指标 内存使用率used_memory 命令处理总数total_commands_processed 延迟时间 内存碎片率 回收key 总结 性能相关的数据指标 通过Red ...
- Chrome JS内存泄漏排查方法(Chrome Profiles)
原文网址:http://blog.csdn.net/kaitiren/article/details/19974269 JS内存泄漏排查方法(Chrome Profiles) Google Ch ...
- Redis性能问题排查解决手册
转自:http://www.cnblogs.com/mushroom/p/4738170.html 阅读目录: 性能相关的数据指标 内存使用率used_memory 命令处理总数total_comma ...
- 内存快照排查OOM,加密时错误方法指定provider方式错误引起的OOM
写在前面: 最近开始总结内存方面的东西,已经总结以前遇到的一些内存案例分享下,接下来还有几篇,然后是进程/线程相关的,逐渐形成我的知识体系树 如果你有兴趣,可以文章末尾的公众号二维码一起梳理这些信息. ...
- Spring Boot引起的“堆外内存泄漏”排查及经验总结
小结: 检索词:C++内存分配器.jvm内存模型.gdb.内存泄露 https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak ...
- 记一次IIS站点出错的解决过程
记一次IIS站点出错的解决过程 以前一直都是人家用着系统出问题了反馈过来这边改,没想到这回就发生在自己使用的过程中 问题经过 我正在执行一个操作,保存了没有返回提示,打开浏览器控制台查看网络,请求返回 ...
随机推荐
- 用 16G 内存存放 30亿数据(Java Map)转载
在讨论怎么去重,提出用 direct buffer 建 btree,想到应该有现成方案,于是找到一个好东西: MapDB - MapDB : http://www.mapdb.org/ 以下来自:ko ...
- bridge模式下 所有容器的 网关 都是 宿主机docker0网卡的ip
bridge模式下 所有容器的 网关 都是 宿主机docker0网卡的ip
- resttemplate 调用https 出错 unable to find valid certification path to requested target
resttemplate 调用https使用下面代码: @Bean @Primary public RestTemplate restTemplate(ClientHttpRequestFactory ...
- 『玩转Streamlit』--上传下载文件
在Web应用中,文件的上传下载是交互中不可缺少的功能. 因为在业务功能中,一般不会只有文字的交互,资料或图片的获取和分发是很常见的需求. 比如,文件上传可让用户向服务器提交数据,如上传图片分享生活.提 ...
- Qt编写视频监控显示安卓版
一.前言 之前就对代码的兼容性做了很好的处理,所以只要开发环境正常,基本的在其他系统比如手机端或者嵌入式linux上重新编译代码即可,最大的难点变成了如何交叉编译对应系统的ffmpeg库,这个在网上有 ...
- Qt音视频开发23-通用视频控件
一.前言 在之前做的视频监控系统中,根据不同的用户需要,做了好多种视频监控内核,有ffmpeg内核的,有vlc内核的,有mpv内核的,还有海康sdk内核的,为了做成通用的功能,不同内核很方便的切换,比 ...
- weixueyuan-Nginx在Kubernetes10
https://www.weixueyuan.net/nginx/kubernetes/ Kubernetes(k8s)分布式容器管理系统简述 Kubernetes 是分布式容器管理系统,它提供了对容 ...
- 第七章 (Nginx+Lua)Web开发实战商品详情页
本章以京东商品详情页为例,京东商品详情页虽然仅是单个页面,但是其数据聚合源是非常多的,除了一些实时性要求比较高的如价格.库存.服务支持等通过AJAX异步加载加载之外,其他的数据都是在后端做数据聚合然后 ...
- 【Netty】(3)—源码NioEventLoopGroup
netty(3)-源码NioEventLoopGroup 一.概念 NioEventLoopGroup对象可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个C ...
- Linux 开发环境常用配置
记录下我个人 Linux 开发环境常用配置,后续可以简单写个小脚本可以把一系列安装配置操作自动化一把. zsh install & conf https://blog.csdn.net/amo ...