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進行操作
參考連結:https://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是什麼意思呢?參見連結:
https://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函式,呼叫的函式,如下圖:
這是非託管的程式碼,函式參考連結:https://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
網站:http://www.ineuos.net
聯絡QQ:504547114
合作微信:wxzz0151
官方部落格:https://www.cnblogs.com/lsjwq
iNeuOS工業網際網路作業系統 公眾號
&n