CSharpFlink分散式實時計算,OutOfMemoryException異常,你意想不到的原因。
目錄
一、測試過程及問題
二、問題排查及分析過程
三、問題分析及解決過程
四、問題解決初步結果
一、測試過程及問題
從昨天15點左右開始測試,1個主節點,10個計算節點,1000個數據點,每個數據點3(1個實時窗口,2個延遲窗口)個數據窗口,每個數據點隨時生成窗口周期和計算實例,每個數據點隨時生成實時數據或歷史數據。
測試結果,由於程式無法再獲得電腦的記憶體而停止工作,更專業的說是System. OutOfMemoryException。
主節點,今天3點左右開始出現異常,如下:
[20-11-13 03:00:21]>>窗口0952-補發數據_CSharpFlink.Core.Window.Operator.Min-執行緒(0033):【2020/11/13 2:00:00-2020/11/13 3:00:00】,異常: Exception of type 'System.OutOfMemoryException' was thrown. at System.Text.StringBuilder.ToString() at CSharpFlink.Core.Task.MasterTaskManager.ParallelCalculate(ICalculateContext context) in \CSharpFlink\src\CSharpFlink.Core\Task\MasterTaskManager.cs:line 358 [20-11-13 03:00:35]>>窗口0927-補發數據_CSharpFlink.Core.Window.Operator.Min-執行緒(0098):【2020/11/13 2:00:00-2020/11/13 3:00:00】,異常: Exception of type 'System.OutOfMemoryException' was thrown. at System.Text.RegularExpressions.Match..ctor(Regex regex, Int32 capcount, String text, Int32 begpos, Int32 len, Int32 startpos) at System.Text.RegularExpressions.RegexRunner.InitMatch() at System.Text.RegularExpressions.RegexRunner.Scan(Regex regex, String text, Int32 textbeg, Int32 textend, Int32 textstart, Int32 prevlen, Boolean quick, TimeSpan timeout) at System.Text.RegularExpressions.Regex.Run(Boolean quick, Int32 prevlen, String input, Int32 beginning, Int32 length, Int32 startat) at System.Text.RegularExpressions.Match.NextMatch() at System.Text.RegularExpressions.RegexReplacement.Replace(Regex regex, String input, Int32 count, Int32 startat) at System.Text.RegularExpressions.Regex.Replace(String input, String replacement) at CSharpFlink.Core.Task.MasterTaskManager.ParallelCalculate(ICalculateContext context) in \CSharpFlink\src\CSharpFlink.Core\Task\MasterTaskManager.cs:line 358 [20-11-13 03:00:42]>>窗口0941-補發數據_CSharpFlink.Core.Window.Operator.Avg-執行緒(0085):【2020/11/13 2:00:00-2020/11/13 3:00:00】,異常: Exception of type 'System.OutOfMemoryException' was thrown. at System.GC.AllocateNewArray(IntPtr typeHandle, Int32 length, Boolean zeroingOptional) at System.GC.AllocateUninitializedArray[T](Int32 length) at System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1.Rent(Int32 minimumLength) at System.Text.ValueStringBuilder.Grow(Int32 additionalCapacityBeyondPos) at System.Text.ValueStringBuilder.Append(ReadOnlySpan`1 value) at System.Text.RegularExpressions.RegexReplacement.Replace(Regex regex, String input, Int32 count, Int32 startat) at System.Text.RegularExpressions.Regex.Replace(String input, String replacement) at CSharpFlink.Core.Task.MasterTaskManager.ParallelCalculate(ICalculateContext context) in \CSharpFlink\src\CSharpFlink.Core\Task\MasterTaskManager.cs:line 358 ValueStringBuilder.Append(ReadOnlySpan`1 value) at System.Text.RegularExpressions.RegexReplacement.Replace(Regex regex, String input, Int32 count, Int32 startat) at System.Text.RegularExpressions.Regex.Replace(String input, String replacement) at CSharpFlink.Core.Task.MasterTaskManager.ParallelCalculate(ICalculateContext context) in \CSharpFlink\src\CSharpFlink.Core\Task\MasterTaskManager.cs:line 358 [20-11-13 03:00:46]>>窗口0970-補發數據_CSharpFlink.Core.Window.Operator.Sum-執行緒(0074):【2020/11/13 2:00:00-2020/11/13 3:00:00】,異常: Exception of type 'System.OutOfMemoryException' was thrown. at System.String.Concat(String str0, String str1) at CSharpFlink.Core.Common.FileUtil.WriteAppend(String filePath, String[] contents) in \CSharpFlink\src\CSharpFlink.Core\Common\FileUtil.cs:line 36 at CSharpFlink.Core.Task.MasterTaskManager.ParallelCalculate(ICalculateContext context) in \CSharpFlink\src\CSharpFlink.Core\Task\MasterTaskManager.cs:line 370
從節點,部分存活,部分異常退出,異常資訊如下:
[20-11-13 02:00:38]>>任務解析異常: Exception of type 'System.OutOfMemoryException' was thrown. at System.String.Concat(String str0, String str1) at CSharpFlink.Core.Common.FileUtil.WriteAppend(String filePath, String[] contents) in \CSharpFlink\src\CSharpFlink.Core\Common\FileUtil.cs:line 36 at CSharpFlink.Core.Task.SlaveTaskManager.AddTask(String taskMsg) in \CSharpFlink\src\CSharpFlink.Core\Task\SlaveTaskManager.cs:line 138
358行的程式碼:
CalculateContext calcContext=(CalculateContext)context;
370行的程式碼:
_masterCacheList.TryAdd(downTrans.Key, compressMsg);
138行的程式碼:
_slaveCacheList.TryAdd(downTrans.Key, downTrans);
masterCacheList和slaveCacheList變數是ConcurrentDictionary類。
二、問題排查及分析過程
共性問題:記錄的每處OutOfMemoryException異常資訊都會涉及到對【String】的操作。
第一步,使用dotnet-dump工具對String進行操作
參考鏈接://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/debug-memory-leak。
System.String有1784359個對象,為什麼這麼多對象呢?因為要生成計算節點的任務,這個任務要臨時保存到文件目錄中,把計算任務的文件發送到計算節點後,再進行刪除和清空程式快取。
寫任務文件其中涉及到FileUtil.WriteAppend()方法,這個和上面異常的日誌資訊是對應的,WriteAppend的程式碼,如下:
public static void WriteAppend(string filePath, string[] contents) { using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) { fs.Seek(fs.Length, SeekOrigin.Current); string content = String.Join(Environment.NewLine, contents) + Environment.NewLine; byte[] data = System.Text.Encoding.UTF8.GetBytes(content); fs.Write(data, 0, data.Length); fs.Close(); } } 註:這是很早寫的程式碼。
其中Join函數,可能涉及到了Concat函數,和異常資訊也是對應的。
那就奇怪了,難道using和Close沒有起來關閉和釋放資源的目的嗎?讓我們來看看FileStream的基類Stream的Dispose和 Close都做了什麼?看源程式碼,如下圖:
從程式碼上看唯一做了SuppressFinalize函數操作,那麼SuppressFinalize是什麼意思呢?參見鏈接:
//docs.microsoft.com/zh-cn/dotnet/api/system.gc.suppressfinalize?view=netcore-3.1。
上面鏈接的大概意思是:請求公共語言運行時不要調用指定對象的終結器。也就是說繼承了IDisposable介面,就不再調用類的析構函數了,那析構函數做了什麼呢?如下圖:
我們分析至此,Dispose和Close相當於什麼都沒有做。那隻能依賴GC來清理資源了。那在高並發下操作FileStream,Dispose和Close不起作用的情況下,難道GC沒有及時回收資源?看來有可能是這個問題。
三、問題分析及解決過程
但是怎麼解決這個問題呢?記得FileStream類有一個Flush函數,具體操作函數程式碼,如下圖:
Flush函數主要調用了FlushOSBuffer函數,程式碼如下圖:
沒有找到FlushFileBuffers函數,調用的函數,如下圖:
這是非託管的程式碼,函數參考鏈接://docs.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers。大致意思是立即把數據寫到磁碟文件中,但是沒有找到該函數的源程式碼。
不管源程式碼的事了,修改一下WriteAppend函數,加上Flush測試一下,程式碼如下:
using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) { fs.Seek(fs.Length, SeekOrigin.Current); string content = String.Join(Environment.NewLine, contents) + Environment.NewLine; byte[] data = System.Text.Encoding.UTF8.GetBytes(content); fs.Write(data, 0, data.Length); fs.Flush(); //新增加程式碼。 fs.Close(); }
四、問題解決初步結果
上午10點部署,測試到下午15點,總共5個小時左右的時間。記憶體使用情況,主節點基本維持在:380 MB(1000數據點,每個數據點有3個數據窗口,如果1個窗口,應該在130 MB左右),子節點基本維持在:150 MB。有一段時間,記憶體會逐步增漲,但是某個時間點記憶體會釋放到基本情況,曲線呈現正弦波趨勢。記憶體使用情況,如下圖:
物聯網&大數據技術 QQ群:54256083
物聯網&大數據合作 QQ群:727664080
聯繫QQ:504547114
合作微信:wxzz0151
官方部落格://www.cnblogs.com/lsjwq
iNeuOS工業互聯網作業系統 公眾號