詳解 Flink 容器化環境下的 OOM Killed
在生產環境中,Flink 通常會部署在 YARN 或 k8s 等資源管理系統之上,程式會以容器化(YARN 容器或 docker 等容器)的方式執行,其資源會受到資源管理系統的嚴格限制。另一方面,Flink 執行在 JVM 之上,而 JVM 與容器化環境並不是特別適配,尤其 JVM 複雜且可控性較弱的記憶體模型,容易導致程式因使用資源超標而被 kill 掉,造成 Flink 應用的不穩定甚至不可用。
針對這個問題,Flink 在 1.10 版本對記憶體管理模組進行了重構,設計了全新的記憶體引數。在大多數場景下 Flink 的記憶體模型和預設已經足夠好用,可以幫使用者遮蔽程式背後的複雜記憶體結構,然而一旦出現記憶體問題,問題的排查和修復都需要比較多的領域知識,通常令普通使用者望而卻步。
為此,本文將解析 JVM 和 Flink 的記憶體模型,並總結在工作中遇到和在社群交流中瞭解到的造成 Flink 記憶體使用超出容器限制的常見原因。由於 Flink 記憶體使用與使用者程式碼、部署環境、各種依賴版本等因素都有緊密關係,本文主要討論 on YARN 部署、Oracle JDK/OpenJDK 8、Flink 1.10+ 的情況。此外,特別感謝 @宋辛童(Flink 1.10+ 新記憶體架構的主要作者)和 @唐雲(RocksDB StateBackend 專家)在社群的答疑,令筆者受益匪淺。
JVM 記憶體分割槽
對於大多數 Java 使用者而言,日常開發中與 JVM Heap 打交道的頻率遠大於其他 JVM 記憶體分割槽,因此常把其他記憶體分割槽統稱為 Off-Heap 記憶體。而對於 Flink 來說,記憶體超標問題通常來自 Off-Heap 記憶體,因此對 JVM 記憶體模型有更深入的理解是十分必要的。
除了上述 Spec 規定的標準分割槽,在具體實現上 JVM 常常還會加入一些額外的分割槽供進階功能模組使用。以 HotSopt JVM 為例,根據 Oracle NMT[5] 的標準,我們可以將 JVM 記憶體細分為如下區域:
Heap: 各執行緒共享的記憶體區域,主要存放 new 運算子建立的物件,記憶體的釋放由 GC 管理,可被使用者程式碼或 JVM 本身使用。
Class: 類的後設資料,對應 Spec 中的 Method Area (不含 Constant Pool),Java 8 中的 Metaspace。
Thread: 執行緒級別的記憶體區,對應 Spec 中的 PC Register、Stack 和 Natvive Stack 三者的總和。
Compiler: JIT (Just-In-Time) 編譯器使用的記憶體。
Code Cache: 用於儲存 JIT 編譯器生成的程式碼的快取。
GC: 垃圾回收器使用的記憶體。
Symbol: 儲存 Symbol (比如欄位名、方法簽名、Interned String) 的記憶體,對應 Spec 中的 Constant Pool。
Arena Chunk: JVM 申請作業系統記憶體的臨時快取區。
NMT: NMT 自己使用的記憶體。
Internal: 其他不符合上述分類的記憶體,包括使用者程式碼申請的 Native/Direct 記憶體。
Unknown: 無法分類的記憶體。
理想情況下,我們可以嚴格控制各分割槽記憶體的上限,來保證程式總體記憶體在容器限額之內。但是過於嚴格的管理會帶來會有額外使用成本且缺乏靈活度,所以在實際中為了 JVM 只對其中幾個暴露給使用者使用的分割槽提供了硬性的上限,而其他分割槽則可以作為整體被視為 JVM 本身的記憶體消耗。
具體可以用於限制分割槽記憶體的 JVM 引數如下表所示(值得注意的是,業界對於 JVM Native 記憶體並沒有準確的定義,本文的 Native 記憶體指的是 Off-Heap 記憶體中非 Direct 的部分,與 Native Non-Direct 可以互換)。
從表中可以看到,使用 Heap、Metaspace 和 Direct 記憶體都是比較安全的,但非 Direct 的 Native 記憶體情況則比較複雜,可能是 JVM 本身的一些內部使用(比如下文會提到的 MemberNameTable),也可能是使用者程式碼引入的 JNI 依賴,還有可能是使用者程式碼 自身透過 sun.misc.Unsafe 申請的 Native 記憶體。理論上講,使用者程式碼或第三方 lib 申請的 Native 記憶體需要使用者來規劃記憶體用量,而 Internal 的其餘部分可以併入 JVM 本身的記憶體消耗。而實際上 Flink 的記憶體模型也遵循了類似的原則。
Flink TaskManager 記憶體模型
首先回顧下 Flink 1.10+ 的 TaskManager 記憶體模型。
顯然,Flink 框架本身不僅會包含 JVM 管理的 Heap 記憶體,也會申請自己管理 Off-Heap 的 Native 和 Direct 記憶體。在筆者看來,Flink 對於 Off-Heap 記憶體的管理策略可以分為三種:
硬限制(Hard Limit): 硬限制的記憶體分割槽是 Self-Contained 的,Flink 會保證其用量不會超過設定的閾值(若記憶體不夠則丟擲類似 OOM 的異常)
軟限制(Soft Limit): 軟限制意味著記憶體使用長期會在閾值以下,但可能短暫地超過配置的閾值。
預留(Reserved): 預留意味著 Flink 不會限制分割槽記憶體的使用,只是在規劃記憶體時預留一部分空間,但不能保證實際使用會不會超額。
結合 JVM 的記憶體管理來看,一個 Flink 記憶體分割槽的記憶體溢位會導致何種後果,判斷邏輯如下:
1、若是 Flink 有硬限制的分割槽,Flink 會報該分割槽記憶體不足。否則進入下一步。
2、若該分割槽屬於 JVM 管理的分割槽,在其實際值增長導致 JVM 分割槽也記憶體耗盡時,JVM 會報其所屬的 JVM 分割槽的 OOM (比如 java.lang.OutOfMemoryError: Jave heap space)。否則進入下一步。
3、該分割槽記憶體持續溢位,最終導致程式總體記憶體超出容器記憶體限制。在開啟嚴格資源控制的環境下,資源管理器(YARN/k8s 等)會 kill 掉該程式。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69989270/viewspace-2753923/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 詳解CentOS5.5 下搭建 PHP 環境(最佳的LAMP環境)CentOSPHPLAMP
- Windows環境下,.lib匯入庫 詳解Windows
- 🔥Appium+python 自動化(二)- 環境搭建—下(超詳解)APPPython
- Hyperledger Fabric 通道配置檔案和容器環境變數詳解變數
- 環境變數詳解變數
- docker 容器環境下的 MySQL 拒絕連線錯誤解決辦法DockerMySql
- 超詳細,Windows系統搭建Flink官方練習環境Windows
- Spring(環境搭建&配置詳解)Spring
- 一次生產環境OOM排查OOM
- Flink 1.10 Container 環境實戰AI
- .NET Core 環境變數詳解變數
- Windows環境下的Nginx環境搭建WindowsNginx
- 詳解Window10下使用IDEA搭建Hadoop開發環境IdeaHadoop開發環境
- flink window詳解
- Appium+python自動化(一)- 環境搭建—上(超詳解)APPPython
- 虛擬化環境下的效能測試
- Flink生產環境常見問題及解決方法
- 容器雲多叢集環境下如何實踐 DevOpsdev
- 容器雲環境下如何設計儲存架構?架構
- 本地環境提交flink on yarn作業Yarn
- Linux環境下:程式的連結, 裝載和庫[ELF檔案詳解]Linux
- Flink Slot詳解與Job Execution Graph優化優化
- Flink 入門篇之 在 Linux 上搭建 Flink 的單機環境Linux
- 容器化環境中,JVM最佳引數配置實踐JVM
- 容器環境持續整合優化,Drone CI 提速 500%優化
- Golang環境變數設定詳解Golang變數
- VSCode + WSL 2 + Ruby環境搭建詳解VSCode
- Cypress系列(14)- 環境變數詳解變數
- Android + Appium 自動化測試完整的環境配置及程式碼詳解AndroidAPP
- windows環境下memcache配置方法 詳細篇Windows
- 如何檢視Docker容器環境變數,如何向容器傳遞環境變數Docker變數
- 詳解vue-cli4環境變數與分環境打包方法Vue變數
- 容器環境下如何將NuGet包XML文件新增到SwaggerXMLSwagger
- Flink生產環境常見問題
- 自動化整合:Kubernetes容器引擎詳解
- IM推送保障及網路優化詳解(三):如何在弱網環境下優化大資料傳輸優化大資料
- DKhadoop環境安裝配置步驟詳解Hadoop
- Linux環境變數詳解與應用Linux變數