摘要:本文主要介紹了FusionInsight Flink元件的基本原理、Flink任務提交的常見問題、以及最佳實踐FAQ。
本文分享自華為雲社群《FusionInsight HD Flink元件基本原理和常見問題解析》,作者:FI小粉絲 。
Flink是一個批處理和流處理結合的統一計算框架,其核心是一個提供資料分發以及並行化計算的流資料處理引擎。
它的最大亮點是流處理,是業界最頂級的開源流處理引擎。
Flink最適合的應用場景是低時延的資料處理(Data Processing)場景:高併發pipeline處理資料,時延毫秒級,且兼具可靠性。
本文主要介紹了FusionInsight Flink元件的基本原理、Flink任務提交的常見問題、以及最佳實踐FAQ。
基本概念
基本原理
簡介
Flink是一個批處理和流處理結合的統一計算框架,其核心是一個提供了資料分發以及並行化計算的流資料處理引擎。它的最大亮點是流處理,是業界最頂級的開源流處理引擎。
Flink最適合的應用場景是低時延的資料處理(Data Processing)場景:高併發pipeline處理資料,時延毫秒級,且兼具可靠性。
Flink技術棧如圖所示:
Flink在當前版本中重點構建如下特性,其他特性繼承開源社群,不做增強,具體請參考:https://ci.apache.org/projects/flink/flink-docs-release-1.4/
- DataStream
- Checkpoint
- Stream SQL
- 視窗
- Job Pipeline
- 配置表
架構
Flink架構如圖所示。
Flink整個系統包含三個部分:
- Client
Flink Client主要給使用者提供向Flink系統提交使用者任務(流式作業)的能力。
- TaskManager
Flink系統的業務執行節點,執行具體的使用者任務。TaskManager可以有多個,各個TaskManager都平等。
- JobManager
Flink系統的管理節點,管理所有的TaskManager,並決策使用者任務在哪些Taskmanager執行。JobManager在HA模式下可以有多個,但只有一個主JobManager。
Flink系統提供的關鍵能力:
- 低時延
提供ms級時延的處理能力。
- Exactly Once
提供非同步快照機制,保證所有資料真正只處理一次。
- HA
JobManager支援主備模式,保證無單點故障。
- 水平擴充套件能力
TaskManager支援手動水平擴充套件。
原理
- Stream & Transformation & Operator
使用者實現的Flink程式是由Stream和Transformation這兩個基本構建塊組成。
- Stream是一箇中間結果資料,而Transformation是一個操作,它對一個或多個輸入Stream進行計算處理,輸出一個或多個結果Stream。
- 當一個Flink程式被執行的時候,它會被對映為Streaming Dataflow。一個Streaming Dataflow是由一組Stream和Transformation Operator組成,它類似於一個DAG圖,在啟動的時候從一個或多個Source Operator開始,結束於一個或多個Sink Operator。
下圖為一個由Flink程式對映為Streaming Dataflow的示意圖。
上圖中“FlinkKafkaConsumer”是一個Source Operator,Map、KeyBy、TimeWindow、Apply是Transformation Operator,RollingSink是一個Sink Operator。
- Pipeline Dataflow
在Flink中,程式是並行和分散式的方式執行。一個Stream可以被分成多個Stream分割槽(Stream Partitions),一個Operator可以被分成多個Operator Subtask。
Flink內部有一個優化的功能,根據上下游運算元的緊密程度來進行優化。
- 緊密度低的運算元則不能進行優化,而是將每一個Operator Subtask放在不同的執行緒中獨立執行。一個Operator的並行度,等於Operator Subtask的個數,一個Stream的並行度(分割槽總數)等於生成它的Operator的並行度。如下圖所示。
Operator
- 緊密度高的運算元可以進行優化,優化後可以將多個Operator Subtask串起來組成一個Operator Chain,實際上就是一個執行鏈,每個執行鏈會在TaskManager上一個獨立的執行緒中執行,如下圖所示。
Operator chain
- 上圖中上半部分表示的是將Source和map兩個緊密度高的運算元優化後串成一個Operator Chain,實際上一個Operator Chain就是一個大的Operator的概念。圖中的Operator Chain表示一個Operator,keyBy表示一個Operator,Sink表示一個Operator,它們通過Stream連線,而每個Operator在執行時對應一個Task,也就是說圖中的上半部分有3個Operator對應的是3個Task。
- 上圖中下半部分是上半部分的一個並行版本,對每一個Task都並行化為多個Subtask,這裡只是演示了2個並行度,sink運算元是1個並行度。
日誌介紹
日誌描述
日誌儲存路徑:
- Executor執行日誌:“${BIGDATA_DATA_HOME}/hadoop/data${i}/nm/containerlogs/application_${appid}/container_{$contid}”
執行中的任務日誌儲存在以上路徑中,執行結束後會基於Yarn的配置確定是否匯聚到HDFS目錄中。
- 其他日誌:“/var/log/Bigdata/flink/flinkResource”
日誌歸檔規則:
- Executor日誌預設50MB滾動儲存一次,最多保留100個檔案,不壓縮。
- 日誌大小和壓縮檔案保留個數可以在FusionInsight Manager介面中配置。
Flink日誌列表
日誌級別
Flink中提供瞭如下表所示的日誌級別。日誌級別優先順序從高到低分別是ERROR、WARN、INFO、DEBUG。程式會列印高於或等於所設定級別的日誌,設定的日誌等級越高,列印出來的日誌就越少。
日誌級別
如果您需要修改日誌級別,請執行如下操作:
- 登入FusionInsight Manager系統。
- 選擇“服務管理 > Flink > 服務配置”。
- “引數類別”下拉框中選擇“全部”。
- 左邊選單欄中選擇所需修改的角色所對應的日誌選單。
- 選擇所需修改的日誌級別。
- 單擊“儲存配置”,在彈出視窗中單擊“確定”使配置生效。
配置完成後立即生效,不需要重啟服務。
日誌格式
常見故障
1. Flink對接kafka-寫入資料傾斜,部分分割槽沒有寫入資料
問題現象與背景
使用FlinkKafkaProducer進行資料生產,資料只寫到了kafka的部分分割槽中,其它的分割槽沒有資料寫入
原因分析
- 可能原因1:Flink寫kafka使用的機制與原生介面的寫入方式是有差別的,在預設情況下,Flink使用了”並行度編號+分割槽數量”取模計算的結果作為topic的分割槽編號。那麼會有以下兩種場景:
- 並行度%分割槽數量=0,表示並行度是kafkatopic分割槽數的一倍或者多倍,資料的寫入每個分割槽資料量是均衡的。
- 並行度%分割槽數量≠0,那麼資料量勢必會在個別分割槽上的資料量產生傾斜。
- 可能原因2:在業務程式碼的部分運算元中使用了keyby()方法,由於現網中的資料流中,每個key值所屬的資料量不一致(就是說某些key的資料量會非常大,有些又非常小)導致每個並行度中輸出的資料流量不一致。從而出現資料傾斜。
解決辦法
原因一:
方法1,調整kafka的分割槽數跟flink的並行度保持一致,即要求kafka的分割槽數與flink寫kafka的sink並行度保持強一致性。這種做法的優勢在於每個並行度僅需要跟每個kafka分割槽所在的 broker保持一個常連結即可。能夠節省每個併發執行緒與分割槽之間排程的時間。
方法2,flink寫kafka的sink的分割槽策略寫成隨機寫入模式,如下圖,這樣資料會隨即寫入topic的分割槽中,但是會有一部分時間損耗線上程向定址,推薦使用方法1。
原因二:
需要調整業務側對key值的選取,例如:可以將key調整為“key+隨機數”的方式,保證Flink的keyby()運算元中每個處理並行度中的資料是均衡的。
2. Flink任務的日誌目錄增長過快,導致磁碟寫滿
問題現象
叢集告警磁碟使用率超過閾值,經過排查發現是taskmanager.out檔案過大導致
原因分析
程式碼中存在大量的print模組,導致taskmanager.out檔案被寫入大量的日誌資訊,taskmanager.out 一般是,業務程式碼加入了 .print的程式碼,需要在程式碼中排查是否有類似於以下的程式碼邏輯:
或者類似於這樣的列印:
如果包含,日誌資訊會持續列印到taskmanager.out裡面。
解決方案
將上圖紅框中的程式碼去掉,或者輸出到日誌檔案中。
3. 任務啟動失敗,報資源不足:Could not allocate all requires slots within timeout of xxxx ms
問題現象
任務啟動一段時間後報錯,例如如下日誌,需要60個資源實際上只有54個。
原因分析
Flink任務在啟動過程中的資源使用是先增長在下降到當前值的,實際在啟動過程中需要的資源量等於每個運算元並行度之和。等到任務開始執行後,Flink會對資源進行合併。
例如如下運算元,在啟動過程中需要“1+6+6+5+3=21”個資源。
但是執行穩定後會降低到6。這個是Flink的機制。假如任務在啟動過程中不滿足21個資源的啟動資源量,任務就會出現NoResourceAvailableException的異常。
解決方案
減少任務的啟動併發,或者將其它任務kill掉再啟動Flink任務。
4. 運算元的部分節點產生背壓,其它節點正常
問題現象
業務執行一段時間以後,運算元的部分節點出現背壓。
原因分析
通過Flink原生頁面排查這個併發的運算元所在的節點,通過上圖我們能夠看出是異常運算元的第44個併發。通過前臺頁面能夠檢視並確認第44併發所在的節點,例如下圖:
通過查詢這個節點在taskmanager列表,例如下圖位置:
整理taskmanager在每個nodemanager節點的數量發現,背壓節點啟動的taskmanager數量過多。
經過排查,該yarn叢集資源相對比較緊張,每個節點啟動的taskmanager數量不一致,如果部分節點啟動的較多容易出現資料傾斜。
解決方案
建議一個節點啟動多個slot。避免多個taskmanager出現在一個nodemanager節點上。啟動方式見:slot優化。
FAQ
Flink如何載入其它目錄的jar包
需求描述
Flink業務一般在執行過程中預設載入的jar包路徑為:“xxx/Flink/flink/lib”的目錄下,如果新增其它路徑的jar包會報錯,如何新增其它外部依賴。
實現方案
建立一個外部的lib目錄,將部分依賴包放到外部lib目錄下,如下圖:
修改啟動指令碼的引數配置指令碼,config.sh將jar包路徑傳給環境變數中。
此時正常啟動任務即可,不需要加其它引數。
HDFS上也能看到第三方jar的目錄。
如何收集任務taskmanager的jstack和pstree資訊
需求描述
在任務執行過程中我們通常需要對taskmanager的程式進行查詢和處理,例如:打jstack,jmap等操作,做這些操作的過程中需要獲取任務的taskmanager資訊。
實現方案
獲取一個nodemanager節點上面所有taskmanager的程式資訊的方法如下:
ps -ef | grep taskmanager | grep -v grep | grep -v "bash -c"
其中紅框中的內容就是taskmanager的程式號,如果一個節點上面存在多個taskmanager那麼這個地方會有多個程式號。獲取到程式號後我們可以針對這個程式號收集jstack或者pstree資訊。
收集jstack
1.通過上面流程獲取到程式資訊,然後從中獲取程式ID和application id,如上圖中程式id為“30047 applicationid為application_1623239745860_0001”。
2.從FI前臺介面獲取這個程式的啟動使用者。如下圖為flinkuser。
3.在對應的nodemanager節點後臺切換到這個使用者,人機使用者機機使用者即可。
4. 進入到節點所在的jdk目錄下
5. 給taskmanager程式打jstack。
不同使用者提交的taskmanager只能由提交任務的使用者打jstack。
收集pstree資訊
使用pstree –p PID 的方式能夠獲取taskmanager的pstree資訊,這個地方提供一個收集指令碼。內容如下:
#!/bin/bash searchPID() { local pids=`ps -ef | grep taskmanager | grep -v grep | grep -v "bash -c" | grep -v taskmanagerSearch.sh | awk '{print $2}'`; time=$(date "+%Y-%m-%d %H:%M:%S") echo "checktime is --------------------- $time" >> /var/log/Bigdata/taskManagerTree.log for i in $pids do local treeNum=$(pstree -p $i | wc -l) echo "$i 's pstree num is $treeNum" >> /var/log/Bigdata/taskManagerTree.log done } searchPID
該指令碼的功能為獲取節點上所有taskmanager pstree的數量,列印結果如下:
slot優化
需求描述
Slot可以認為是taskmanager上面一塊獨立分配的資源,是taskmanager並行執行的能力的體現。Taskmanager中有兩種使用slot的方法:
- 一個taskmanager中設定了一個slot。
- 一個taskmanager中設定了多個slot。
每個task slot 表示TaskManager 擁有資源的一個固定大小的子集。假如一個taskManager 有三個slot,那麼它會將其管理的記憶體分成三份給各個slot。資源slot化意味著一個subtask 將不需要跟來自其他job 的subtask 競爭被管理的記憶體,取而代之的是它將擁有一定數量的記憶體儲備。需要注意的是,這裡不會涉及到CPU 的隔離,slot 目前僅用來隔離task 的受管理的記憶體。通過調整task slot 的數量,允許使用者定義subtask 之間隔離的方式。如果一個TaskManager 一個slot,那將意味著每個task group執行在獨立的JVM 中(該JVM可能是通過一個特定的容器啟動的),而一個TaskManager 多個slot 意味著更多的subtask 可以共享同一個JVM。而在同一個JVM 程式中的task 將共享TCP 連線(基於多路複用)和心跳訊息。它們也可能共享資料集和資料結構。因此,對於資源密集型任務(尤其是對cpu使用較為密集的)不建議使用單個taskmanager中建立多個slot使用,否則容易導致taskmanager心跳超時,出現任務失敗。如果需要設定單taskmanager多slot,參考如下操作。
單taskmanager多slot的設定方法
方式一:在配置檔案中配置taskmanager.numberOfTaskSlots,通過修改提交任務的客戶端配置檔案中的配置flink-conf.yaml配置,如下圖:將該值設定為需要調整的數值即可。
方式二:啟動命令的過程中使用-ys命令傳入,例如以下命令:
./flink run -m yarn-cluster -p 1 -ys 3 ../examples/streaming/WindowJoin.jar
在啟動後在一個taskmanager中會啟動3個slot。
單taskmanager多slot需要優化哪些引數
設定單taskmanager多slot需要優化以下引數