Flink基礎系列24-Flink的Window 一.Flink Window 二.Flink Window API 三.代碼測試 參考:

一.Flink Window

1.1 概述

streaming流式計算是一種被設計用於處理無限數據集的數據處理引擎,而無限數據集是指一種不斷增長的本質上無限的數據集,而window是一種切割無限數據爲有限塊進行處理的手段。

Window是無限數據流處理的核心,Window將一個無限的stream拆分成有限大小的”buckets”桶,我們可以在這些桶上做計算操作。

舉例子:假設按照時間段劃分桶,接收到的數據馬上能判斷放到哪個桶,且多個桶的數據能並行被處理。(遲到的數據也可判斷是原本屬於哪個桶的)

1.2 Window類型

  1. 時間窗口(Time Window)
    1)滾動時間窗口
    2)滑動時間窗口
    3)會話窗口
  2. 計數窗口(Count Window)
    1)滾動計數窗口
    2)滑動計數窗口

TimeWindow:按照時間生成Window
CountWindow:按照指定的數據條數生成一個Window,與時間無關

滾動窗口(Tumbling Windows)

  1. 依據固定的窗口長度對數據進行切分
  2. 時間對齊,窗口長度固定,沒有重疊

滑動窗口(Sliding Windows)

  1. 可以按照固定的長度向後滑動固定的距離
  2. 滑動窗口由固定的窗口長度和滑動間隔組成
  3. 可以有重疊(是否重疊和滑動距離有關係)
  4. 滑動窗口是固定窗口的更廣義的一種形式,滾動窗口可以看做是滑動窗口的一種特殊情況(即窗口大小和滑動間隔相等)

會話窗口(Session Windows)

  1. 由一系列事件組合一個指定時間長度的timeout間隙組成,也就是一段時間沒有接收到新數據就會生成新的窗口
  2. 特點:時間無對齊

二.Flink Window API

2.1 概述

  1. 窗口分配器——window()方法

  2. 我們可以用.window()來定義一個窗口,然後基於這個window去做一些聚合或者其他處理操作。
    注意window()方法必須在keyBy之後才能使用。

  3. Flink提供了更加簡單的.timeWindow()和.countWindow()方法,用於定義時間窗口和計數窗口。

DataStream<Tuple2<String,Double>> minTempPerWindowStream = 
  datastream
  .map(new MyMapper())
  .keyBy(data -> data.f0)
  .timeWindow(Time.seconds(15))
  .minBy(1);

窗口分配器(window assigner)

  1. window()方法接收的輸入參數是一個WindowAssigner
  2. WindowAssigner負責將每條輸入的數據分發到正確的window中
  3. Flink提供了通用的WindowAssigner
    1)滾動窗口(tumbling window)
    2)滑動窗口(sliding window)
    3)會話窗口(session window)
    4)全局窗口(global window)

創建不同類型的窗口

  1. 滾動時間窗口(tumbling time window)
    .timeWindow(Time.seconds(15))
  2. 滑動時間窗口(sliding time window)
    timeWindow(Time.seconds(15),Time.seconds(5))
  3. 會話窗口(session window)
    .window(EventTimeSessionWindows.withGap(Time.minutes(10)))
  4. 滾動計數窗口(tumbling count window)
    .countWindow(5)
  5. 滑動計數窗口(sliding count window)
    .countWindow(10,2)

DataStream的windowAll()類似分區的global操作,這個操作是non-parallel的(並行度強行爲1),所有的數據都會被傳遞到同一個算子operator上,官方建議如果非必要就不要用這個API

2.2 TimeWindow

TimeWindow將指定時間範圍內的所有數據組成一個window,一次對一個window裏面的所有數據進行計算。

滾動窗口
Flink默認的時間窗口根據ProcessingTime進行窗口的劃分,將Flink獲取到的數據根據進入Flink的時間劃分到不同的窗口中。

DataStream<Tuple2<String, Double>> minTempPerWindowStream = dataStream 
  .map(new MapFunction<SensorReading, Tuple2<String, Double>>() { 
    @Override 
    public Tuple2<String, Double> map(SensorReading value) throws Exception {
      return new Tuple2<>(value.getId(), value.getTemperature()); 
    } 
  }) 
  .keyBy(data -> data.f0) 
  .timeWindow( Time.seconds(15) ) 
  .minBy(1);

時間間隔可以通過Time.milliseconds(x),Time.seconds(x),Time.minutes(x)等其中的一個來指定。

滑動窗口
滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是window_size,一個是sliding_size。

下面代碼中的sliding_size設置爲了5s,也就是說,每5s就計算輸出結果一次,每一次計算的window範圍是15s內的所有元素。

DataStream<SensorReading> minTempPerWindowStream = dataStream 
  .keyBy(SensorReading::getId) 
  .timeWindow( Time.seconds(15), Time.seconds(5) ) 
  .minBy("temperature");

時間間隔可以通過Time.milliseconds(x),Time.seconds(x),Time.minutes(x)等其中的一個來指定。

2.3 CountWindow

CountWindow根據窗口中相同key元素的數量來觸發執行,執行時只計算元素數量達到窗口大小的key對應的結果。

** 注意:CountWindow的window_size指的是相同Key的元素的個數,不是輸入的所有元素的總數。**

滾動窗口
默認的CountWindow是一個滾動窗口,只需要指定窗口大小即可,當元素數量達到窗口大小時,就會觸發窗口的執行。

DataStream<SensorReading> minTempPerWindowStream = dataStream 
  .keyBy(SensorReading::getId) 
  .countWindow( 5 ) 
  .minBy("temperature");

滑動窗口
滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是window_size,一個是sliding_size。

下面代碼中的sliding_size設置爲了2,也就是說,每收到兩個相同key的數據就計算一次,每一次計算的window範圍是10個元素。

DataStream<SensorReading> minTempPerWindowStream = dataStream 
  .keyBy(SensorReading::getId) 
  .countWindow( 10, 2 ) 
  .minBy("temperature");

2.4 window function

window function 定義了要對窗口中收集的數據做的計算操作,主要可以分爲兩類:

  1. 增量聚合函數(incremental aggregation functions)
  2. 全窗口函數(full window functions)

增量聚合函數

  1. 每條數據到來就進行計算,保持一個簡單的狀態。(來一條處理一條,但是不輸出,到窗口臨界位置才輸出)
  2. 典型的增量聚合函數有ReduceFunction, AggregateFunction。

全窗口函數

  1. 先把窗口所有數據收集起來,等到計算的時候會遍歷所有數據。(來一個放一個,窗口臨界位置才遍歷且計算、輸出)
  2. ProcessWindowFunction,WindowFunction。

2.5 其他可選API

  1. .trigger() ——觸發器
    定義window 什麼時候關閉,觸發計算並輸出結果

  2. .evitor() ——移除器
    定義移除某些數據的邏輯

  3. .allowedLateness() ——允許處理遲到的數據

  4. .sideOutputLateData() ——將遲到的數據放入側輸出流

  5. .getSideOutput() ——獲取側輸出流

三.代碼測試

3.1 測試滾動時間窗口的增量聚合函數

增量聚合函數,特點即每次數據過來都處理,但是到了窗口臨界才輸出結果。

Java代碼:

package org.flink.window;

import org.flink.beans.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

/**
 * @author : 只是甲
 * @date   : 2021-09-16
 * @remark : 測試滾動時間窗口的增量聚合函數
 */
public class WindowTest1_TimeWindow {
    public static void main(String[] args) throws Exception {

        // 創建執行環境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 並行度設置1,方便看結果
        env.setParallelism(1);

        //        // 從文件讀取數據
        //        DataStream<String> dataStream = env.readTextFile("/tmp/Flink_Tutorial/src/main/resources/sensor.txt");

        // 從socket文本流獲取數據
        DataStream<String> inputStream = env.socketTextStream("10.31.1.122", 7777);

        // 轉換成SensorReading類型
        DataStream<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        // 開窗測試

        // 1. 增量聚合函數 (這裏簡單統計每個key組裏傳感器信息的總數)
        DataStream<Integer> resultStream = dataStream.keyBy("id")
                //                .countWindow(10, 2);
                //                .window(EventTimeSessionWindows.withGap(Time.minutes(1)));
                //                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                //                .timeWindow(Time.seconds(15)) // 已經不建議使用@Deprecated
                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                .aggregate(new AggregateFunction<SensorReading, Integer, Integer>() {

                    // 新建的累加器
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    // 每個數據在上次的基礎上累加
                    @Override
                    public Integer add(SensorReading value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    // 返回結果值
                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    // 分區合併結果(TimeWindow一般用不到,SessionWindow可能需要考慮合併)
                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                });

        resultStream.print("result");

        env.execute();
    }
}

運行Java程序,查看結果

啓動Flink程序,在socket窗口輸入數據

輸入(下面用“換行”區分每個15s內的輸入,實際輸入時無換行)

sensor_1,1547718199,35.8
sensor_6,1547718201,15.4

sensor_7,1547718202,6.7
sensor_10,1547718205,38.1
sensor_1,1547718207,36.3
sensor_1,1547718209,32.8

sensor_1,1547718212,37.1

輸出(下面用“換行”區分每個15s內的輸出,實際輸出無換行)
因爲代碼實現每15s一個window,所以"sensor_1"中間一組才累計2,最初一次不累計,最後一次也是另外的window,重新從1計數。

result> 1
result> 1

result> 1
result> 1
result> 2

result> 1

3.2 測試滾動時間窗口的全窗口函數

全窗口函數,特點即數據過來先不處理,等到窗口臨界再遍歷、計算、輸出結果。

代碼:

package org.flink.window;

import org.flink.beans.SensorReading;
import org.apache.commons.collections.IteratorUtils;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

/**
 * @author : 只是甲
 * @date   : 2021-09-16
 * @remark : 測試滾動時間窗口的全窗口函數
 */
public class WindowTest2_TimeWindow {
    public static void main(String[] args) throws Exception {

        // 創建執行環境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 並行度設置1,方便看結果
        env.setParallelism(1);

//        // 從文件讀取數據
//        DataStream<String> dataStream = env.readTextFile("/tmp/Flink_Tutorial/src/main/resources/sensor.txt");

        // 從socket文本流獲取數據
        DataStream<String> inputStream = env.socketTextStream("10.31.1.122", 7777);

        // 轉換成SensorReading類型
        DataStream<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        // 2. 全窗口函數 (WindowFunction和ProcessWindowFunction,後者更全面)
        SingleOutputStreamOperator<Tuple3<String, Long, Integer>> resultStream2 = dataStream.keyBy(SensorReading::getId)
                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
//                .process(new ProcessWindowFunction<SensorReading, Object, Tuple, TimeWindow>() {
//                })
                .apply(new WindowFunction<SensorReading, Tuple3<String, Long, Integer>, String, TimeWindow>() {
                    @Override
                    public void apply(String s, TimeWindow window, Iterable<SensorReading> input, Collector<Tuple3<String, Long, Integer>> out) throws Exception {
                        String id = s;
                        long windowEnd = window.getEnd();
                        int count = IteratorUtils.toList(input.iterator()).size();
                        out.collect(new Tuple3<>(id, windowEnd, count));
                    }
                });

        resultStream2.print("result2");

        env.execute();
    }
}

啓動遠程 nc

nc -lk 7777

在本地socket輸入,查看Flink輸出結果
輸入(以“空行”表示每個15s時間窗口內的輸入,實際沒有“空行”)

sensor_1,1547718199,35.8
sensor_6,1547718201,15.4

sensor_7,1547718202,6.7
sensor_10,1547718205,38.1
sensor_1,1547718207,36.3
sensor_1,1547718209,32.8

輸出(以“空行”表示每個15s時間窗口內的輸入,實際沒有“空行”)
這裏每個window都是分開計算的,所以第一個window裏的sensor_1和第二個window裏的sensor_1並沒有累計。

result2> (sensor_1,1612190820000,1)
result2> (sensor_6,1612190820000,1)

result2> (sensor_7,1612190835000,1)
result2> (sensor_1,1612190835000,2)
result2> (sensor_10,1612190835000,1)

2.3 測試滑動計數窗口的增量聚合函數

滑動窗口,當窗口不足設置的大小時,會先按照步長輸出。
eg:窗口大小10,步長2,那麼前5次輸出時,窗口內的元素個數分別是(2,4,6,8,10),再往後就是10個爲一個窗口了。

代碼:

package org.flink.window;

import org.flink.beans.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * @author : 只是甲
 * @date   : 2021-09-16
 * @remark : 測試滑動計數窗口的增量聚合函數
 */
public class WindowTest3_CountWindow {
    public static void main(String[] args) throws Exception {

        // 創建執行環境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 並行度設置1,方便看結果
        env.setParallelism(1);

        // 從socket文本流獲取數據
        DataStream<String> inputStream = env.socketTextStream("10.31.1.122", 7777);

        // 轉換成SensorReading類型
        DataStream<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        DataStream<Double> resultStream = dataStream.keyBy(SensorReading::getId)
                .countWindow(10, 2)
                .aggregate(new MyAvgFunc());

        resultStream.print("result");

        env.execute();
    }

    public static class MyAvgFunc implements AggregateFunction<SensorReading, Tuple2<Double, Integer>, Double> {

        @Override
        public Tuple2<Double, Integer> createAccumulator() {
            return new Tuple2<>(0.0, 0);
        }

        @Override
        public Tuple2<Double, Integer> add(SensorReading value, Tuple2<Double, Integer> accumulator) {
            // 溫度累加求和,當前統計的溫度個數+1
            return new Tuple2<>(accumulator.f0 + value.getTemperature(), accumulator.f1 + 1);
        }

        @Override
        public Double getResult(Tuple2<Double, Integer> accumulator) {
            return accumulator.f0 / accumulator.f1;
        }

        @Override
        public Tuple2<Double, Integer> merge(Tuple2<Double, Integer> a, Tuple2<Double, Integer> b) {
            return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
        }
    }
}

啓動遠程nc服務

nc -lk 7777

本地socket輸入,Flink控制檯查看輸出結果

輸入
這裏爲了方便,就只輸入同一個keyBy組的數據sensor_1

sensor_1,1547718199,1
sensor_1,1547718199,2
sensor_1,1547718199,3
sensor_1,1547718199,4
sensor_1,1547718199,5
sensor_1,1547718199,6
sensor_1,1547718199,7
sensor_1,1547718199,8
sensor_1,1547718199,9
sensor_1,1547718199,10
sensor_1,1547718199,11
sensor_1,1547718199,12
sensor_1,1547718199,13
sensor_1,1547718199,14

輸出
輸入時,會發現,每次到達一個窗口步長(這裏爲2),就會計算得出一次結果。

第一次計算前2個數的平均值

第二次計算前4個數的平均值

第三次計算前6個數的平均值

第四次計算前8個數的平均值

第五次計算前10個數的平均值

第六次計算前最近10個數的平均值

第七次計算前最近10個數的平均值

result> 1.5
result> 2.5
result> 3.5
result> 4.5
result> 5.5
result> 7.5
result> 9.5

3.4 其他可選API代碼片段

// 3. 其他可選API
OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
};

SingleOutputStreamOperator<SensorReading> sumStream = dataStream.keyBy("id")
  .timeWindow(Time.seconds(15))
  //                .trigger() // 觸發器,一般不使用 
  //                .evictor() // 移除器,一般不使用
  .allowedLateness(Time.minutes(1)) // 允許1分鐘內的遲到數據<=比如數據產生時間在窗口範圍內,但是要處理的時候已經超過窗口時間了
  .sideOutputLateData(outputTag) // 側輸出流,遲到超過1分鐘的數據,收集於此
  .sum("temperature"); // 側輸出流 對 溫度信息 求和。

// 之後可以再用別的程序,把側輸出流的信息和前面窗口的信息聚合。(可以把側輸出流理解爲用來批處理來補救處理超時數據)

參考:

  1. https://www.bilibili.com/video/BV1qy4y1q728
  2. https://ashiamd.github.io/docsify-notes/#/study/BigData/Flink/%E5%B0%9A%E7%A1%85%E8%B0%B7Flink%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0?id=_62-window-api
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章