记一次Memory Leak分析

 起因:最近公司的一个web产品遇到了内存溢出,于是开始着手调查。

调查:首先当务之急是找到那个或那些API导致Memory Leak,这个应该不难,根据监控分析,在内存上升时间段内有哪些API被访问,再就是根据开发人员提供信息,评估可能内存溢出的API。

案例分析:划定API范围后,用压力测试来重现问题,进一步定位具体是哪个方法,哪个技术点出问题,这些应该也不难,难的是知道什么地方有问题,但不知道怎么解决。

下面是经过精简后,分离出的有问题的代码。

项目文件csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <ServerGarbageCollection>false</ServerGarbageCollection>
    <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="ClosedXML" Version="0.96.0" />
    <PackageReference Include="QRCoder" Version="1.4.2" />
  </ItemGroup>
</Project>

具体代码program.cs:

using ClosedXML.Excel;
using DocumentFormat.OpenXml.Spreadsheet;
using QRCoder;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Color = System.Drawing.Color;


Console.WriteLine("回车开始");
Console.ReadLine();
var qrGenerator = new QRCodeGenerator();
while (true)
{
    using var workBook = new XLWorkbook();
    var ws = workBook.Worksheets.Add("TestSheet");
    for (var i = 1; i < 200; i++)
    {
        using var qrCodeData = qrGenerator.CreateQrCode($"{i}_{DateTime.Now.ToString("yyMMddHHmmssfffffff")}_01PAe8np5m7pVULUiuxwwZTWQ9KZ8JUgQyWiyUrsiHqi4FKrCzhRAcddCkkJKDgVEkpmqD7kYJz5GTpe4oHvJdJDnNMMCTwbV19G", QRCodeGenerator.ECCLevel.L);
        using var qrCode = new QRCode(qrCodeData);
        using var qrCodeImage = qrCode.GetGraphic(20, Color.Black, Color.White, false);
        using var imgStream = new MemoryStream();
        qrCodeImage.Save(imgStream, ImageFormat.Png);
        using var image = ws.AddPicture(imgStream).MoveTo(ws.Cell(i, 1), new Point(50, 10));
        image.Width = 60;
        image.Height = 60;
    }
    workBook.SaveAs(@$"/app/images/{i}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx");
    Console.WriteLine($"完了:{DateTime.Now}");
    Console.ReadLine();
}

很有趣的是,这块代码在docker里运行时会有memory leak,但在windows上是没有问题的。

上面代码主要有两个功能,生成二维码,然后保存在Excel里,最后保存本地。具体表现是,每生成一个Excel,内存就会增加,不释放,直到容器重启。

其实找性能问题是一个排查的过程,首先要一点一点把认为有问题的代码修正,再次进行测试,看是否有改善,比如主动去释放一些对象,使用using,或者直接用GC.Collect()来测试。经过一轮后发现没有改善内存只涨不降的现象。于是就进行功能分享,把QR生成换成静态图片,查看Excel生成部分是否有问题,这样很快就定位到了QR生成有问题,但QR生成能有什么问题呢?并且这个功能一直在用,从.net 5用到.net 6,都没有变更过这里的代码。那只能查看是QR生成有无问题了。

下面是经过一番查找后的结果,果然ORCode类对.net 6.0不能很好的支持,只能换掉它了,替代的方式是用PngByteQRCode。

https://github.com/codebude/QRCoder/wiki/Advanced-usage---QR-Code-renderers#2-overview-of-the-different-renderers

f49be545e7b92960a87128242d26953e.png

修改后的代码是:

using ClosedXML.Excel;
using DocumentFormat.OpenXml.Spreadsheet;
using QRCoder;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Color = System.Drawing.Color;


Console.WriteLine("回车开始");
Console.ReadLine();
var qrGenerator = new QRCodeGenerator();
while (true)
{
    using var workBook = new XLWorkbook();
    var ws = workBook.Worksheets.Add("TestSheet");
    for (var i = 1; i < 200; i++)
    {      
        byte[] qrCodeAsBitmapByteArr = PngByteQRCodeHelper.GetQRCode(DateTime.Now.ToString("yyMMddHHmmssfffffff") + "01PAe8np5m7pVULUiuxwwZTWQ9KZ8JUgQyWiyUrsiHqi4FKrCzhRAcddCkkJKDgVEkpmqD7kYJz5GTpe4oHvJdJDnNMMCTwbV19G", QRCodeGenerator.ECCLevel.Q, 20, false);
        var imgStream = new MemoryStream(qrCodeAsBitmapByteArr);
        imgStream.Seek(0, SeekOrigin.Begin);
        using var image = ws.AddPicture(imgStream).MoveTo(ws.Cell(i, 1), new Point(50, 10));
        image.Width = 60;
        image.Height = 60;
    }
    workBook.SaveAs(@$"C:\Users\axzxs\Pictures\{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx");
    Console.WriteLine($"完了:{DateTime.Now}");
    Console.ReadLine();
}

其实单看上面过程,很简单,其实测试过程中的一些指标表象,以及个人对技术点的认知,都会让性能分析起来要走一些弯路。比如上面功能,其实我们花了很多时间在Excel的生成找问题,甚至换了Excel生成组件来进行测试,经过多轮出修改方案,修改代码,压力测试,结果分析,才最终定义QR码,又经过多轮对QR码生成的方案优化,最后才找到是因为QR不支持导致的结果。

性能分析就是找疑难杂症,是个痛苦过程,同时,解决掉,又是一个很喜悦的收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值