zoukankan      html  css  js  c++  java
  • 【.Net Core】分析.net core在linux下内存占用过高问题--持续更新

    现象

    随着程序运行,内存占用率越来越高,直到触发linux的OOM,程序被杀死。

    分析工具

    运行环境:.net core 3.1(微软的分析工具要求最低3.0,无法分析2.1的core程序,需要先改为core 3.1才能分析)

    linux:ubuntu 18

    分析工具:dotnet-counters, dotnet-dump

    工具的安装见:https://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/dotnet-counters

    分析过程

    1,获取要分析进程的pid

    使用top或者ps等等工具,获取程序的pid

    对于docker环境,如果没有安装top命令,可以使用如下安装

    apt-get install procps

    2,查看内存使用情况(我这里pid为13156)

    dotnet-counters monitor -p 13156

    从结果来看,GC中的Gen2占用了较多的内存,理论上,不应该有很多的Gen2,我们需要分析一下Gen2里面到底是什么?

    Gen0,Gen1,Gen2以及LOH的区别,以及.net core内存管理机制,见:

    https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-5.0

    3,获取进程的dump文件

    dotnet-dump collect -p 13156

    说明:要使用这条命令获取dump,如果在docker中,需要提供docker的--private参数,如果是在AWS的ECS中使用的Fargate模式运行,则不支持此参数。需要在EC2上运行。

    此命令会在当前目录生成一个dump文件

    4,分析dump文件

    dotnet-dump analyze core_20210510_054712
    # 分析gen2中的内容,每个命令的参数以及和含义,可以使用help查看
    dg gen2

     从结果来看,有很多string类型的数据在gen2中,以及mysql的一些数据,我们打开看看具体是什么内容

    看输出,有很多一样的内容,我们随便打开一个看看

    可以看到内容就是数据库的返回数据

     同样的方法,我们看看哪些string里面都是什么

    有非常多的对象,我们也是随便打开一个看看内容

    看着像是web的打印

    总结

    获取dump文件

    dotnet-dump collect <pid>

    分析dump文件

    dotnet-analyze xxxxx

    获取gen2或者其他的内存数据

    dg gen2 | gen1 | gen1 | genloh

    查看内存数据类型

    dumpheap -mt xxxxxx

    查看内存数据的具体内容

    do xxxxxx

    通过具体内容,配合开发人员定位代码问题

    -------------------------------------------

    2021-5-11 更新

    再说明一下我们这边的运行环境,以及代码中用到的相关服务

    我们正式程序运行在AWS基于Fargate的ECS上,容器配置为0.5vCPU, 512MB内存,.net core程序版本为2.1,数据库查询使用sqlsugar,aws服务用到了Dynamodb和SNS

    问题就是程序运行大约1天就出现OOM,导致容器重启。

    下面是我们排查问题的过程

    Round 1:将core从2.1升级到3.1

    原因:根据微软的说法,3.0以后优化了core在linux下以及容器中的性能,降低了内存占用,详见下面的连接。

    https://devblogs.microsoft.com/dotnet/using-net-and-docker-together-dockercon-2019-update/

    说明:2.1升级到3.1还是有很多地方需要修改的,微软这方面做的就不够好,但是3.1升级5.0据说改动不大,这里要感谢我们的开发同学积极配合修改了代码。

    结论:内存增长速度为原来的一半。

    内存增长速度变慢了,但是仍然在增长,没有解决根本问题。

    Round 2:调整GC模式,从默认的Server GC调整为WorkStation GC

    原因:WorkStaionGC会使用更少的内存,回收的更频繁,但是性能可能会稍差一下,根据微软的说法,在docker环境中还是推荐使用WorkStaion GC模式,两种GC的对比,以及推荐详见下文:

    https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-3.1#workstation-gc-vs-server-gc

    结论:内存增长速度又降低了一半,但是仍然在增长,还是没有解决根本问题

    Round 3:Gen2中的内存到底是什么?

    原因:既然内存不停的在涨,而且通过分析工具可以看到主要是GC中的Gen2部分在增长,按照微软的说法内存中的垃圾数据根据时间的长短,依次存入Gen0, Gen1, Gen2。而且Gen1和Gen2是由core进行垃圾回收的,不需要我们干预,那么Gen2中的内容到底是什么?为什么一直没有被回收?

    这一部分就是上面的文章内容,通过以上方法我们已经知道Gen2中的内存主要是变量、数据库查询结果、console控制台打印。奇了怪了,为什么这些东西会在内存里不释放?

    关于core的内存管理以及GC原理,见下面的文章

    https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-3.1

    结论:找到了内存中的数据,但是不解这些数据为什么没有被回收

    Round 4:关闭这些打印看看

    原因:既然Gen2中存在大量的控制台打印,那么我如果关闭控制台打印呢?是不是就没有这部分的内存占用了

    结论:没啥作用,内存仍然在不停的增长

    Round 5:是不是数据库工具有问题?

    原因:既然内存中有大量的数据库查询结果,那么是不是因为我们用了sqlsugar导致的?sqlsugar本身有什么缓存的机制?

    我们查了sqlsugar的官方,sqlsugar确实支持二级缓存,但是我们没有用上,详见官方文档:

    https://www.donet5.com/home/Doc?typeId=1214

    为此,我们直接删除了sqlsugar部分代码,不查询数据库了,直接写死在代码里返回。然后开始跑压力测试(这时候接口已经没有业务逻辑了)

    结论:没啥用,内存还在增长

    Round 6:放大招了,写一个空接口,没有任何逻辑,直接返回固定字符串

    原因:做减法不行,我们开始做加法,从0开始写接口,一点点功能添加,看看到底添加到哪一步的时候,导致内存增加

    结论:老实了,内存终于不增长了(准确的说增长的非常缓慢,一晚上增加了0.2%的内存)

    至少说明core本身在docker环境下运行,没有明显的内存泄露问题,问题应该出在代码逻辑上

    Round 7:使用环境变量限制core的内存使用

    原因:开发修改代码去了,我趁机再尝试一次,根据微软的说法,core也是会尽可能多的使用系统内存,以提高性能:

    默认情况下,当物理内存负载达到 90%时,垃圾回收对于执行完整的压缩垃圾回收变得更加积极,以避免分页。 当内存负载低于 90% 时,GC 优先使用后台回收进行完整的垃圾回收,这种方法的暂停时间较短,但不会使堆的总大小减少太多。

    文档见:https://docs.microsoft.com/zh-cn/dotnet/core/run-time-config/garbage-collector#high-memory-percent

    所以,我给docker添加了两个环境变量,理论上只有一个会生效。环境变量的值为16进制。我设置限制内存使用为50%

    COMPlus_GCHighMemPercent   0x32
    COMPlus_GCHeapHardLimitPercent  0x32

    运行结果:

    红框部分为添加了环境变量的曲线,对比其他曲线,没有变化。

    结论:没啥用

    ###############################

    今天到此为止,等我们后续的尝试出了结果再更新,希望我们的测试过程对大家有些参考。

    ------------------------------------------------

     2021-05-13 更新

    经过几轮从0开始增加代码的测试,终于定位到问题了:在请求的header校验中,静态变量不释放导致!

    解决办法:每次接口校验header时,设置一遍缓存给局部变量,不要每次都new新的缓存。

    原创作者:郑立赛


    邮箱:zhenglisai@qq.com


    欢迎关注我们的公众号获取最新文章:运维自动化开发


    公众号
    公众号
  • 相关阅读:
    LeetCode 258 Add Digits
    LeetCode 231 Power of Two
    LeetCode 28 Implement strStr()
    LeetCode 26 Remove Duplicates from Sorted Array
    LeetCode 21 Merge Two Sorted Lists
    LeetCode 20 Valid Parentheses
    图形处理函数库 ImageTTFBBox
    php一些函数
    func_get_arg(),func_get_args()和func_num_args()的用法
    人生不是故事,人生是世故,摸爬滚打才不会辜负功名尘土
  • 原文地址:https://www.cnblogs.com/zhenglisai/p/14751677.html
Copyright © 2011-2022 走看看