中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

記一次.NET內存居高不下排查解決與啟示

前情

我們有個海外的項目,一共70個服務,前前后后花了超過一年時間完成了云服務遷移和架構調整。主要是架構調整了,原來的docker swarm托管服務,幾臺云服務器將n個服務堆在一起,只會對服務器資源做整體監控,服務器沒事沒人管單個服務的內存情況。新架構對單個服務改動不大,但外部相關架構則改頭換面了,最大的改動是容器改為Kubernetes托管,放在AWS的EKS上。新架構在新的云服務上線后,暴露了很多內存問題。在此拿某個服務根據解決過程,給個排查思路,希望能給到大家一點啟示。

問題

服務為一個普通的ASP.NET Core gRPC工程,平常沒什么流量。設置的最大副本數為5,生產環境服務啟動后,Pod內存達到或超過K8s內存請求值(512Mi),自動觸發擴展到5個實例,即副本數達到最大。這與QA環境表現并不一樣,也就沒有在測試階段發現。需要想辦法復現、排查解決高副本和高內存占用問題。

部署里面對容器的資源,容器資源只做了對CPU和內存的預留設置(request),沒有做上限設置(llimit)。從內存曲線上看,很多副本的內存甚至超過了請求的512Mi。不過有一點很奇怪,服務的接口并沒有出現性能下降的跡象。

 

問題復現

嘗試在QA環境對相關接口進行壓測,問題能復現,表現為HPA副本擴展后各個POD的內存居高不下,維持在500~600Mi長時間不釋放,有時候壓測甚至能沖到800Mi。即使沒有什么接口請求,也需要超過20個小時才緩慢下降到350Mi左右。但嘗試本地VS診斷工具則并沒有發現什么內存不釋放問題,除了一些個大對象駐留問題。

 

代碼與dotnet-dump

因為其他類似的服務并不會這樣,所以第一時間懷疑是代碼問題,但這么想是錯的,下面交代。懷疑代碼問題后,想著是不是有什么內存泄漏,找了個服務的接口在QA壓測后問題能復現(即內存長時間不釋放)。看了好幾遍代碼,除了一些個ToList用的太過頻繁并沒什么問題(也與內存問題不相關),用VS診斷工具檢查內存有運行又沒內存泄露問題。于是在QA環境用dotnet-dump把內存快照下載回來分析,找到了個大對象堆LOH駐留類型的類,而且VS診斷工具找到的類是同一個,接著定位到了接口調用這個類的地方。業務調用很單純,這個類就從數據庫用Dapper查出來得到列表,然后分組計算下數據,不會有什么內存泄露的機會;但注釋掉此部分查詢則內存不再上升到500~600Mi,只在300Mi左右,而且內存使用率下降也變快了。繼續二分法+注釋大法調試,最后只保留數據庫查詢語句而不做后續業務處理,連引用都不做,內存還是會達到500~600Mi。這就讓人摸不著頭腦了,代碼肯定是相關的,數據庫查詢幾下一列表數據都能讓內存達到預留臨界值(request),列表也才約11000條數據,雖然確實是LOH對象,但不至于造成這么嚴重的內存不釋放現象。

 

GC調參

代碼摸不著頭腦,就想辦法調試下GC。 

方案一: 定時調用GC.Collect來回收垃圾

加入定時執行GC.Collect()后,內存占用能立即回落,這方案似乎也可以

 

方案二:GC配置調整:配置內存限制感知-DOTNET_GCHeapHardLimit

添加環境變量DOTNET_GCHeapHardLimit=0x20000000 # 512Mi的十六進制值,能限制內存的使用,但并不能讓GC能敏感地進行回收,方案失敗

 

方案三:切換為GC場景類型到工作站

原默認為Server GC,指定為Workstation GC后,內存占用不到180Mi,擴容縮容正常,這方案看起來也可以

 

調試結束,方案一和方案三似乎可行,查了相關資料后,兩個方案其實都有問題。方案一是代碼主動強制執行了垃圾回收,但大多數情況下并不被建議在代碼里面去執行,因為執行GC.Collect多多少少會影響服務性能,GC自己能感知內存使用率然后自動進行執行回收。至于方案三,不同的模式本來就對應著不同的服務場景,服務本身就是后端接口,切換為工作站模式也許可行,但ASP.NET Core默認就是Server GC,Server GC模式本身為了支持高性能并不會頻繁執行垃圾回收(從.NET 9開始不一樣,.NET 9的ASP.NET Core默認是,.NET 8也支持這種模式,只不過不是默認的)。

 

為容器限定內存上限

查資料過程中才了解K8s的資源設置是有預留設置(request,又稱請求設置)和上限設置(limit),服務只設置了請求request部分,沒有limit部分,那有沒有可能是服務容器因為沒有被設置內存limit,導致GC如脫韁野馬般豪氣地使用內存呢?那為啥內存不釋放?就是Server GC感覺內存還是夠用的,具體文章參考:和。先查詢下可用內存吧,于是加個下面接口查詢:

app.MapGet("/runtime/memory", () =>
{
    return GC.GetGCMemoryInfo().TotalAvailableMemoryBytes.ToString("N0");
});

結果返回:

可用內存居然高達4Gi,真相很接近了。接著為服務設置內存limit為512Mi,再次查詢得到可用內存為512Mi。沒錯!就是少設置了內存上限,沒有這個,此時可用內存為節點內存(4GB);加了limit重新壓測,曲線:

事件如下:

程序內存釋放正常!副本數釋放也正常!另外接口響應時間沒有受到影響,問題得到解決!

 

總結

服務內存曲線高居不下是因為容器沒有被限制內存,K8s沒有指定內存limit,可用內存就是節點/宿主機的物理內存,高達4GB。沒有設置內存limit,但是設置了HPA,于是服務一啟動經過一些時間內存超過HPA閾值造成副本數增加;GC默認是Server GC,其感知的內存足夠所以不釋放(包括小對象和大對象)。雖然主動調用GC.Collect則可以釋放,但一般不會這樣做,因為GC有自己的一套邏輯。限定內存為0.5Gi后,內存釋放曲線正常,HPA擴縮正常,響應時間正常,問題得到解決,也能解釋服務的接口并沒有出現性能下降的的現象。

啟示

如果遇到類似內存居高不下問題,先確定.NET版本極其GC是Server GC還是Workstation GC。然后再確定其分配的可用內存是多少,K8s下要檢查其資源limit有沒有被設置。如果被設置之后依然有內存不釋放/泄露問題,再懷疑代碼問題。

 

參考:

 

posted @ 2025-03-03 13:50  朝野布告  閱讀(3841)  評論(12)    收藏  舉報