如何分析EFCore引发的内存泄漏
調(diào)查實(shí)體框架核心中的內(nèi)存泄漏
不要讓內(nèi)存泄漏成為洪水術(shù)語“內(nèi)存泄漏”和“ .NET應(yīng)用程序”不是經(jīng)常一起使用。但是,我們最近在一個(gè).NET Core Web應(yīng)用程序中出現(xiàn)了一系列內(nèi)存不足異常。事實(shí)證明,此問題是由Entity Framework Core中的行為更改引起的,盡管最終的解決方案非常簡單,但實(shí)現(xiàn)該目標(biāo)的過程既充滿挑戰(zhàn)又有趣。
該系統(tǒng)本身托管在Azure中,由Angular SPA前端和后端的.NET Core API組成,使用Entity Framework Core與Azure SQL數(shù)據(jù)庫進(jìn)行通信。作為專門從事.NET開發(fā)的軟件咨詢公司,我們之前已經(jīng)編寫了許多類似的應(yīng)用程序。因此,內(nèi)存不足崩潰[1]是無法預(yù)料的,因此我們立即知道這是需要認(rèn)真對(duì)待的事情。使用Azure門戶中的指標(biāo),我們可以看到內(nèi)存使用率穩(wěn)步上升,然后突然下降:此下降是應(yīng)用程序崩潰。
修復(fù)之前?因此,我們花了一些時(shí)間進(jìn)行調(diào)查并逐步進(jìn)行更改,以解決看似經(jīng)典的內(nèi)存泄漏問題。.NET泄漏的常見原因是未正確處理某些問題,在我們的案例中很可能是EF Core數(shù)據(jù)庫上下文。因此,我們遍歷了源代碼,以尋找可能無法處理上下文的潛在原因。這變成了空白。
我們將Entity Framework Core升級(jí)到了最新版本,因?yàn)樽罱母掳ǜ鞣N內(nèi)存泄漏的修復(fù)程序和總體效率的提高。
我們還在使用的Application Insights版本中發(fā)現(xiàn)了可能的內(nèi)存泄漏(請(qǐng)參閱https://github.com/microsoft/ApplicationInsights-dotnet/issues/594),因此我們也對(duì)該軟件包進(jìn)行了升級(jí)。
這些都不能解決問題,因此我們解剖了從Azure應(yīng)用服務(wù)中獲取的內(nèi)存轉(zhuǎn)儲(chǔ)(請(qǐng)參閱https://blogs.msdn.microsoft.com/jpsanders/2017/02/02/how-to-get-a-full-memory-dump-in-azure-app-services/)。
我們注意到,絕大多數(shù)托管內(nèi)存最終都由MemoryCache類使用。進(jìn)一步深入研究表明,大多數(shù)緩存數(shù)據(jù)都是原始SQL查詢的形式。我們看到大量的根本上是同一查詢的事件被多次緩存,并且參數(shù)本身被硬編碼在查詢中而不是被參數(shù)化。
例如,與其像這樣緩存查詢:
SELECT TOP (1) UserId, FirstName, LastName, EmailAddress FROM Users WHERE UserId = @param_1我們發(fā)現(xiàn)這樣的多個(gè)查詢:
SELECT TOP (1) UserId, FirstName, LastName, EmailAddress FROM Users WHERE UserId = 5因此,我們進(jìn)行了一些搜索,尋找可能與之相關(guān)的EF核心問題,并遇到了這個(gè)問題:https[2]?:?//github.com/aspnet/EntityFrameworkCore/issues/10535[3]。
關(guān)于這個(gè)問題的主題指出了這個(gè)問題:我們正在建立一個(gè)動(dòng)態(tài)表達(dá)式樹,并使用它Expressions.Expression.Constant?來為where子句提供參數(shù)。使用常量表達(dá)式意味著Entity Framework Core不會(huì)參數(shù)化SQL查詢,并且是Entity Framework 6的行為更改。
我們到處都使用這個(gè)表達(dá)式樹,通過它的ID來獲取某些東西,這就是為什么它是一個(gè)很大的問題。
因此,這就是我們所做的更改:
// Before var param = Expressions.Expression.Parameter(typeof(T)); Expression = Expressions.Expression.Lambda<Func<T, bool>>(Expressions.Expression.Call(Expressions.Expression.Constant(valuesToFilter),"Contains",Type.EmptyTypes,Expressions.Expression.Property(param, propertyName)),param); // After var param = Expressions.Expression.Parameter(typeof(T)); // This is what we added Expression<Func<List<int>>> valuesToFilterLambda = () => valuesToFilter; Expression = Expressions.Expression.Lambda<Func<T, bool>>(Expressions.Expression.Call(valuesToFilterLambda.Body,"Contains",Type.EmptyTypes,Expressions.Expression.Property(param, propertyName)),param);使用lambda表達(dá)式獲取表達(dá)式主體會(huì)使Entity Framework Core[4]對(duì)SQL查詢進(jìn)行參數(shù)化,因此僅緩存它的一個(gè)實(shí)例。
這是包括修訂版本在內(nèi)的一段時(shí)間內(nèi)的內(nèi)存使用情況。該版本以紅色標(biāo)記,您可以看到差異很大。穩(wěn)定的內(nèi)存使用量從未超過200MB,而不斷攀升至超過1GB,然后發(fā)生崩潰。
修復(fù)后最初進(jìn)行調(diào)查時(shí),真正的解決方案不是我們要注意的事情,而是通過檢查內(nèi)存轉(zhuǎn)儲(chǔ)并遵循證據(jù)我們最終到達(dá)那里。
從此調(diào)查中可以汲取的教訓(xùn)是:
?內(nèi)存轉(zhuǎn)儲(chǔ)不會(huì)說謊-如果內(nèi)存泄漏,請(qǐng)先查看證據(jù)。?微軟已經(jīng)開放了EF Core的源代碼,所有問題在那里所有人都可以看到,對(duì)有需求的開發(fā)者來說非常方便。?簡單的代碼更改(在這種情況下為一行)可能會(huì)產(chǎn)生巨大的影響。
References
[1]?,內(nèi)存不足崩潰:?https://dzone.com/articles/what-causes-outofmemoryerror
[2]?https:?https://github.com/aspnet/EntityFrameworkCore/issues/10535
[3]?//github.com/aspnet/EntityFrameworkCore/issues/10535:?https://github.com/aspnet/EntityFrameworkCore/issues/10535
[4]?Entity Framework Core:?https://dzone.com/articles/entity-framework-core-30-and-sql-server-2019-perfo
總結(jié)
以上是生活随笔為你收集整理的如何分析EFCore引发的内存泄漏的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: async,await执行流看不懂?看完
- 下一篇: 微服务统计,分析,图表,监控, 分布式追