大家好,今天我們來聊一聊flink的Watermark機制。
這也是flink系列的的第一篇文章,如果對flink、大資料感興趣的小夥伴,記得點個關注呀。
背景
flink作為先進的流水計算引擎,提供了三種時間概念,這對基於時間的流處理應用提供了多種可能。
-
Event time 指生產裝置中每個獨立的事件發生的時間,比如使用者點選產生的時間。
-
Process time 指正在執行相關程式的機器的系統時間。
-
IngestionTime 指事件進入flink的時間。
WaterMark機制主要是用來解決EventTime亂序的情況。從事件的產生、到經過訊息中介軟體、然後經過data source和Operator,在傳輸的過程中,由於網路傳輸等原因,會導致EventTime出現亂序,如果只是根據EventTime來決定window的執行,我們不能明確資料是否已經全部到位,所以我們需要有一個機制來保證特定的時間後,必須觸發window去執行計算了,這個機制就是Watermark。
定義
WaterMark是一種特殊的時間戳,它會被插入到資料流中,用於表示EventTime小於Watermark的事件全部落入到了相應的視窗中。
如圖所示,這是一個視窗大小為5的亂序流。w(5)表示EventTime小於5的資料已經落入相應的視窗。當Watermark大於等於視窗的最大時間戳(即視窗的endTime),就會觸發相應視窗的計算。比如W(5)大於等於5,會觸發視窗[0,5)的計算。
生成
WaterMark有兩種生成方式,分別是Punctuated Watermark(標點水位線)和Periodic Watermark(週期性水位線)。
-
標點水位線
標點水位線(Punctuated Watermark)是通過資料流中某些特殊標記事件來觸發新水位線的生成。這種方式下,視窗的觸發與時間無關,而是決定於何時收到標記事件。在實際的生產中Punctuated方式在TPS很高的場景下會產生大量的Watermark在一定程度上對下游運算元造成壓力,所以只有在實時性要求非常高的場景才會選擇Punctuated的方式進行Watermark的生成。
-
週期性水位線
週期性的(允許一定時間間隔或者達到一定的記錄條數)產生一個Watermark。水位線提升的時間間隔是由使用者設定的,在兩次水位線提升時隔內會有一部分訊息流入,使用者可以根據這部分資料來計算出新的水位線。在實際的生產中Periodic的方式必須結合時間和積累條數兩個維度繼續週期性產生Watermark,否則在極端情況下會有很大的延時。
案例
在實際的專案中,主要是使用週期性的水位線,我們可以通過env.getConfig().setAutoWatermarkInterval()設定,預設是200ms。
public class test {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.getConfig().setAutoWatermarkInterval(100);
DataStreamSource<String> inputStream = env.socketTextStream("localhost", 8888);
SerializableTimestampAssigner<String> timestampAssigner =
new SerializableTimestampAssigner<String>(){
@Override
public long extractTimestamp(String element, long recordTimestamp) {
String[] fields = element.split(" ");
Long aLong = new Long(fields[0]);
return aLong * 1000L;
}
};
SingleOutputStreamOperator<Tuple2<String,Long>> result=inputStream.assignTimestampsAndWatermarks(
WatermarkStrategy
.<String>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner(timestampAssigner)
).map(new MapFunction<String, Tuple2<String,Long>>() {
@Override
public Tuple2<String, Long> map(String s) {
return Tuple2.of(s.split(" ")[1],1L);
}
}).keyBy(0)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.reduce(new ReduceFunction<Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> reduce(Tuple2<String, Long> stringLongTuple2, Tuple2<String, Long> t1) throws Exception {
return new Tuple2<>(stringLongTuple2.f0,stringLongTuple2.f1+t1.f1);
}
});
result.print();
env.execute("warter mark test");
}
}
當通過nc -l 8888輸入資料
1630312530 java
1630312533 java
1630312536 java
1630312540 java
1630312543 java
1630312538 java
1630312545 java
1630312539 java
1630312550 java
1630312549 java
1630312555 java
輸出為:
1> (java,5)
1> (java,4)
當事件“1630312545 java”進入流處理後,生成的Watermark為“W(1630312540)”,大於等於視窗[1630312530,1630312540)的endTime,觸發視窗的計算,此時延遲資料“1630312538 java”也會被計算在內,所以會輸出“(java,5)”,而事件“1630312539 java”是在Watermark已經觸發相應的視窗計算後,才進入流處理中,延遲太久,會被忽略掉。當事件“163031255 java”進入流處理後,生成的Wartermark為W(163031250),觸發視窗[163031240,163031250)的計算。
最後
到此為止,我們已經把Watermark機制聊完了,如果喜歡,請點個關注吧。
更多有趣知識,請關注公眾號【程式設計師學長】。我給你準備了上百本學習資料,包括python、java、資料結構和演算法等。如果需要,請關注公眾號【程式設計師學長】,回覆【資料】,即可得。
你知道的越多,你的思維也就越開闊,我們下期再見。