flink的watermark機制你學會了嗎?

公眾號程式設計師學長發表於2021-08-31

大家好,今天我們來聊一聊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、資料結構和演算法等。如果需要,請關注公眾號【程式設計師學長】,回覆【資料】,即可得。

你知道的越多,你的思維也就越開闊,我們下期再見。

相關文章