業務上需要將同一個時間段的兩種數據收集到一起做一些計算,這兩種數據分別存在於Kafka的兩個Topic中。計算邏輯是這樣的:
使用兩個DataStream分別消費兩個Topic中的數據,對兩條流先分別設置WaterMark,然後union,接着進行keyBy操作,最後使用Window將同一個時間窗口中的兩種數據匯聚在一起進行計算。但是發現程序無論是在本地運行還是在yarn-cluster模式下運行,只要並行度不爲1,程序都不能正常執行。明明顯示已經收到了數據,但是Window就是不觸發:
Window何時觸發:
Window是通過Trigger來觸發的,時間使用EventTime時默認使用EventTimeTrigger:
每條數據進來的時候都會通過WindowOperator中的processElement()方法走到onElement()方法中。如果當前WaterMark大於窗口結束時間,那麼就會立即觸發窗口計算,否則會使用窗口結束時間註冊一個觸發器(Timer),用於觸發Window的計算(底層是Set結構,所以觸發器會覆蓋從而不會內存溢出):
WaterMark何時更新:
在對流使用assignTimestampsAndWatermarks之後,會對流中的元素調用用戶定義的方法,然後生成WaterMark:
再將WaterMark broadcast出去:
broadcast出去的目的是爲了下游再檢查WaterMark的時候,以最小的那個WaterMark作爲總體的WaterMark:
這裏可以看到,最小的watermark居然是個負值(在調試了好一會之後),也就是說至少有一個任務WaterMark一直沒有更新,也就是說並行度爲3的task裏面有task沒有接受到數據
Flink是如何消費Kafka數據的:
Flink source用到了FlinkKafkaConsumer010,沒有指定KafkaPartitioner的話,會通過FixedPartitioner來給出默認的partitioner方法,而默認的Flink partition的規則,就是Flink的並行度ID除以kafka partition length取餘。程序的並行度爲3,Kafka的Partition數量也爲3,即每個Task消費一個Partition中的數據:
public int partition(T record, byte[] key, byte[] value, String targetTopic, int[] partitions) { Preconditions.checkArgument(partitions != null && partitions.length > 0, "Partitions of the target topic is empty."); return partitions[this.parallelInstanceId % partitions.length]; }
然後使用命令檢查發現,有兩個Partition是沒有數據的,所以導致兩個Task的WaterMark一直更新不了:
解決方法:
將WaterMark的並行度與Source的並行度設置不一致,使得數據進行Shuffle,從而使得Task都可以更新WaterMark,最終結果如下:
參考:
https://www.jianshu.com/p/753e8cf803bb(問題定位:Flink水位線不觸發問題)
https://www.cnblogs.com/ljygz/p/11392952.html
https://www.jianshu.com/p/c8c789ff5570(EventTimeTrigger解析)