记一次使用Magicodes.IE.Pdf引起的内存泄漏

时间:05/06/2023 23:36:39   作者:ChenReal    阅读:317

什么是Magicodes.IE.Pdf?

不少数.Neter用过Magicodes.IE(麦扣)系列的组件做Excel导入/导出,即使没用过至少也听说吧。。Magicodes.IE.Pdf便是其中负责生成PDF的组件。不得不说这轮子封装的很好,开箱即用,还有Razor模版引擎的加持,非常符合Web开发人员的使用习惯。使得原本编写模板输出PDF文件及其枯燥无聊的过程,变得十分轻松愉快。

内存泄漏!

然而再好的组件工具,总免不了会有坑,Magicodes.IE.Pdf也不例外。这回我遇到的坑,还是个大坑、深坑,这便是臭名昭著的“内存泄漏”!

话说我的项目更新发布3个小时后,应用占内存高达700M+,而且还有继续往上飙升的趋势,没过一会就突破900M的大关。可能有人会这么猜测,会不会是用户很多并发超大?非也,这个应用按理应该是非常轻量的,用户不过10人,压根谈不上并发。有人还会继续猜测,也许不一定是PDF组件的问题,也可能是系统某个业务声明的对象事例过多,资源没有释放造成的。有道理,但可能性不是很大,横向对比我们其他项目便可排出以上假设,因为用同一套后端底层框架已经十分成熟了,被多套项目验证过。曾经做过几百人同时在线的电商交易系统,业务复杂程度和QPS都不是当前项目可以比的。但是,在其他项目中内存表现的很平稳,最多不过240M。对比第三方组件差异,最值得怀疑的地方就是我在这个问题项目中,弄了一个Magicodes.IE.Pdf来生成PDF文件了。

排查解决~

经过以上的分析,和几轮代码排查&自我否定后,问题终于被锁定了。

因为,每次调用生成PDF的API方法都创建了一次PdfExporter对象实例,然后每次创建的事例所占用的内存均无法释放,慢慢累积下来,就造成了严重的内存泄漏事故。

[HttpGet]
public async Task CreatePDF()
{
        var tplPath = Path.Combine(Directory.GetCurrentDirectory(), \"TestFiles\", \"ExportTemplates\",
             \"receipt.cshtml\");
        var tpl = System.IO.File.ReadAllText(tplPath);
        var exporter = new PdfExporter();
        //此处使用默认模板导出
        var result = await exporter.ExportByTemplate(\"test.pdf\",
                new StudentDto
                {
                        Amount = 22939.43M,
                        Grade = \"2019秋\",
                        IdNo = \"43062619890622xxxx\",
                        Name = \"张三\",
                        Payee = \"湖南心莱信息科技有限公司\",
                        PaymentMethod = \"微信支付\",
                        Profession = \"运动训练\",
                        Remark = \"学费\",
                        TradeStatus = \"已完成\",
                        TradeTime = DateTime.Now,
                        UppercaseAmount = \"贰万贰仟玖佰叁拾玖圆肆角叁分\",
                        Code = \"19071800001\"
                }, tpl);
}

以上就是官方给的示例,照抄了过来然后就芭比Q了!苍天呐,找谁说理去呢?随后,我改为只声明一个对象实例,所有请求都重用这个对象方法生成PDF。

private IPdfExporter exporter;

[HttpGet]
public async Task CreatePDF()
{
        var tplPath = Path.Combine(Directory.GetCurrentDirectory(), \"TestFiles\", \"ExportTemplates\",
             \"receipt.cshtml\");
        var tpl = System.IO.File.ReadAllText(tplPath);
        if(exporter == null) exporter = new PdfExporter();
        //此处使用默认模板导出
        var result = await exporter.ExportByTemplate(\"test.pdf\",
                new StudentDto
                {
                        Amount = 22939.43M,
                        Grade = \"2019秋\",
                        IdNo = \"43062619890622xxxx\",
                        Name = \"张三\",
                        Payee = \"湖南心莱信息科技有限公司\",
                        PaymentMethod = \"微信支付\",
                        Profession = \"运动训练\",
                        Remark = \"学费\",
                        TradeStatus = \"已完成\",
                        TradeTime = DateTime.Now,
                        UppercaseAmount = \"贰万贰仟玖佰叁拾玖圆肆角叁分\",
                        Code = \"19071800001\"
                }, tpl);
}

最终结果,应用运行了超过一天,内存稳定在300M+,并没有不断往上攀升,问题解决!吸取教训:用第三方组件需谨慎,尽量不要偷懒照抄人家随手写的sample代码,因为别人写个实例只是告诉你相关API怎么调用,并不会帮你考虑性能并发合适否有内存泄漏的风险。因此,需要结合自己的经验和实际项目的情况,更科学加以封装然后再做集成。

还能优化么?

对于这样一个小规模的应用,一口气吃了300M+的内存,还是让我感到有些于心不忍!是否进一步优化空间呢?遗憾的是,找了一遍PdfExporter类,并没有找到销毁对象释放内存相关的方法。

需要下载源代码然后入侵性低修改封装?呃,这种事情我不太爱干。算了,要不直接放弃Magicodes.IE.Pdf,换其他组件试试看效果如何。也许基于HTML生成PDF这套方案本身就不是不可能做到省内存吧,像iText和QuestPDF这一类的PDF组件,走的是另外一条技术路线,不依赖HTML直接生成PDF或许性能上会更加好。

测试了一下QuestPDF,果真还是有明显效果的。内存使用在180M左右徘徊,性能杠杠的!是个好东西,我准备“重用”它!

 

评论
0/200