記一次Memory Leak分析

起因:最近公司的一個web產品遇到了記憶體溢位,於是開始著手調查。

調查:首先當務之急是找到那個或那些API導致Memory Leak,這個應該不難,根據監控分析,在記憶體上升時間段內有哪些API被訪問,再就是根據開發人員提供資訊,評估可能記憶體溢位的API。

案例分析:劃定API範圍後,用壓力測試來重現問題,進一步定位具體是哪個方法,哪個技術點出問題,這些應該也不難,難的是知道什麼地方有問題,但不知道怎麼解決。

下面是經過精簡後,分離出的有問題的程式碼。

專案檔案csproj:

Exe net6。0 enable enable false true

具體程式碼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

記一次Memory Leak分析

修改後的程式碼是:

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不支援導致的結果。

效能分析就是找疑難雜症,是個痛苦過程,同時,解決掉,又是一個很喜悅的收穫。