前情

我们有个海外的项目,一共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有没有被设置。如果被设置之后依然有内存不释放/泄露问题,再怀疑代码问题。

参考:

为容器和 Pod 分配内存资源

Pod 和容器的资源管理

为 Pod 配置服务质量

工作站和服务器垃圾回收

内存没有被回收

动态适应应用程序大小 (DATAS)

了解 Fargate Pod 配置详细信息

记一次.NET内存居高不下排查解决与启示的更多相关文章

  1. 记一次java内存溢出的解决过程

    注:本文主要记录这次解决内存溢出问题的过程而不是具体问题. 最近在写一个搜索引擎,使用倒排索引结构进行文档检索,保存索引的基本思想是先将倒排列表保存到内存中一个有序Map里(TreeMap),然后当内 ...

  2. 解决WebBrowser控件会导致应用程序占用内存居高不下问题

    WebBrowser 是一个 .NET 控件类,在 .NET Framework 2.0 版中新增.WebBrowser 类使用户可以在窗体中导航网页,下面我们来看在开发中碰到WebBrowser控件 ...

  3. java内存溢出的解决思路

    原文地址:https://www.cnblogs.com/200911/p/3965108.html 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能 ...

  4. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  5. Redis性能问题排查解决手册(七)

     阅读目录: 性能相关的数据指标 内存使用率used_memory 命令处理总数total_commands_processed 延迟时间 内存碎片率 回收key 总结 性能相关的数据指标 通过Red ...

  6. Chrome JS内存泄漏排查方法(Chrome Profiles)

     原文网址:http://blog.csdn.net/kaitiren/article/details/19974269 JS内存泄漏排查方法(Chrome Profiles)   Google Ch ...

  7. Redis性能问题排查解决手册

    转自:http://www.cnblogs.com/mushroom/p/4738170.html 阅读目录: 性能相关的数据指标 内存使用率used_memory 命令处理总数total_comma ...

  8. 内存快照排查OOM,加密时错误方法指定provider方式错误引起的OOM

    写在前面: 最近开始总结内存方面的东西,已经总结以前遇到的一些内存案例分享下,接下来还有几篇,然后是进程/线程相关的,逐渐形成我的知识体系树 如果你有兴趣,可以文章末尾的公众号二维码一起梳理这些信息. ...

  9. Spring Boot引起的“堆外内存泄漏”排查及经验总结

    小结: 检索词:C++内存分配器.jvm内存模型.gdb.内存泄露 https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak ...

  10. 记一次IIS站点出错的解决过程

    记一次IIS站点出错的解决过程 以前一直都是人家用着系统出问题了反馈过来这边改,没想到这回就发生在自己使用的过程中 问题经过 我正在执行一个操作,保存了没有返回提示,打开浏览器控制台查看网络,请求返回 ...

随机推荐

  1. Dockerr安装Oracle以及使用DBeaver连接

    拉取镜像 pull container-registry.oracle.com/database/free:latest 创建容器 说明一下我现在的最新版本是23 docker run -d --na ...

  2. 【转】ReentrantReadWriteLock读写锁详解

    https://www.cnblogs.com/xiaoxi/p/9140541.html 一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁.在没有写操作的时 ...

  3. 跟着 8.6k Star 的开源数据库,搞 RAG!

    过去 9 年里,HelloGitHub 月刊累计收录了 3000 多个开源项目.然而,随着项目数量的增加,不少用户反馈:"搜索功能不好用,找不到想要的项目!" 这让我意识到,仅仅收 ...

  4. Python读取栅格图像并对像元数据处理后导出到表格文件中

      本文介绍基于Python语言中的gdal模块,读取一景.tif格式的栅格遥感影像文件,提取其中每一个像元的像素数值,对像素值加以计算(辐射定标)后,再以一列数据的形式将计算后的各像元像素数据保存在 ...

  5. UML之关联

    关联指两个类之间的各种联系.UML使用各种单实线表示关联,这个单实线可以是直线(垂直的.水平的或者倾斜的).折线甚至曲线. 事实上,关联也是展示类的属性的另一外的一种形式.例如在下图中,我们通过一条实 ...

  6. [转]Error: Node Sass does not yet support your current environment: Windows 64-bit

    错误日志:Error: Node Sass does not yet support your current environment: Windows 64-bit with Unsupported ...

  7. [转]swing中如何将jtable中的数据导入到excel中?

    这个版本的代码是可以支持中文,需要导入jxl.jar包,并添加到Build Path中(自行搜索下载). 最终代码: package test; import java.awt.event.*; im ...

  8. IM跨平台技术学习(三):vivo的Electron技术栈选型、全方位实践总结

    本文由vivo技术团队Yang Kun分享,原题"electron 应用开发优秀实践",本文有修订. 1.引言 在上篇<Electron初体验(快速开始.跨进程通信.打包.踩 ...

  9. 使用学生优惠创建 Azure Database for MySQL 数据库

    前言 在此之前,你需要拥有一个已通过学生认证的 Azure 账户.关于通过 Azure 学生认证,网上已有大量教程,此处不再赘述. 前些日子认证通过了 Azure 的学生认证,在部署此网站时发现 Az ...

  10. 性能测试工具_nGrinder

    1. ngrinder-controller-3.4.3.war 放置到tomcat的webapps目录下:2. 启动tomcat;3. 访问地址: http://localhost:8080/ngr ...