大數據開發-Flink-窗口全解析

Flink窗口背景

Flink認爲Batch是Streaming的一個特例,因此Flink底層引擎是一個流式引擎,在上面實現了流處理和批處理。而Window就是從Streaming到Batch的橋樑。通俗講,Window是用來對一個無限的流設置一個有限的集合,從而在有界的數據集上進行操作的一種機制。流上的集合由Window來劃定範圍,比如“計算過去10分鐘”或者“最後50個元素的和”。Window可以由時間(Time Window)(比如每30s)或者數據(Count Window)(如每100個元素)驅動。DataStream API提供了Time和Count的Window。

一個Flink窗口應用的大致骨架結構如下所示:

// Keyed Window
stream
  .keyBy(...)               <-  按照一個Key進行分組
  .window(...)              <-  將數據流中的元素分配到相應的窗口中
  [.trigger(...)]            <-  指定觸發器Trigger(可選)
  [.evictor(...)]            <-  指定清除器Evictor(可選)
    .reduce/aggregate/process()      <-  窗口處理函數Window Function
// Non-Keyed Window
stream
  .windowAll(...)           <-  不分組,將數據流中的所有元素分配到相應的窗口中
  [.trigger(...)]            <-  指定觸發器Trigger(可選)
  [.evictor(...)]            <-  指定清除器Evictor(可選)
    .reduce/aggregate/process()      <-  窗口處理函數Window Function

Flink窗口的骨架結構中有兩個必須的兩個操作:

  • 使用窗口分配器(WindowAssigner)將數據流中的元素分配到對應的窗口。
  • 當滿足窗口觸發條件後,對窗口內的數據使用窗口處理函數(Window Function)進行處理,常用的Window Function有reduceaggregateprocess

滾動窗口

基於時間驅動

將數據依據固定的窗口長度對數據進行切分,滾動窗口下窗口之間之間不重疊,且窗口長度是固定的。我們可以用TumblingEventTimeWindowsTumblingProcessingTimeWindows創建一個基於Event Time或Processing Time的滾動時間窗口。窗口的長度可以用org.apache.flink.streaming.api.windowing.time.Time中的secondsminuteshoursdays來設置。

//關鍵處理案例
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapStream.keyBy(0);
// 基於時間驅動,每隔10s劃分一個窗口
WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> timeWindow =
keyedStream.timeWindow(Time.seconds(10));
// 基於事件驅動, 每相隔3個事件(即三個相同key的數據), 劃分一個窗口進行計算
// WindowedStream<Tuple2<String, Integer>, Tuple, GlobalWindow> countWindow =
keyedStream.countWindow(3);
// apply是窗口的應用函數,即apply裏的函數將應用在此窗口的數據上。
timeWindow.apply(new MyTimeWindowFunction()).print();
// countWindow.apply(new MyCountWindowFunction()).print();

基於事件驅動

當我們想要每100個用戶的購買行爲作爲驅動,那麼每當窗口中填滿100個”相同”元素了,就會對窗口進行計算,很好理解,下面是一個實現案例

public class MyCountWindowFunction implements WindowFunction<Tuple2<String, Integer>,
  String, Tuple, GlobalWindow> {
    @Override
    public void apply(Tuple tuple, GlobalWindow window, Iterable<Tuple2<String, Integer>>
      input, Collector<String> out) throws Exception {
      SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
      int sum = 0;
      for (Tuple2<String, Integer> tuple2 : input){
        sum += tuple2.f1;
      }
      
      //無用的時間戳,默認值爲: Long.MAX_VALUE,因爲基於事件計數的情況下,不關心時間。
      long maxTimestamp = window.maxTimestamp();
      out.collect("key:" + tuple.getField(0) + " value: " + sum + "| maxTimeStamp :"+ maxTimestamp + "," + format.format(maxTimestamp)
      );
  }
}

滑動時間窗口

動窗口是固定窗口的更廣義的一種形式,滑動窗口由固定的窗口長度和滑動間隔組成,特點:窗口長度固定,可以有重疊,滑動窗口以一個步長(Slide)不斷向前滑動,窗口的長度固定。使用時,我們要設置Slide和Size。Slide的大小決定了Flink以多大的頻率來創建新的窗口,Slide較小,窗口的個數會很多。Slide小於窗口的Size時,相鄰窗口會重疊,一個事件會被分配到多個窗口;Slide大於Size,有些事件可能被丟掉

基於時間的滾動窗口

//基於時間驅動,每隔5s計算一下最近10s的數據
// WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> timeWindow =
keyedStream.timeWindow(Time.seconds(10), Time.seconds(5));
SingleOutputStreamOperator<String> applyed = countWindow.apply(new WindowFunction<Tuple3<String, String, String>, String, String, GlobalWindow>() {
    @Override
    public void apply(String s, GlobalWindow window, Iterable<Tuple3<String, String, String>> input, Collector<String> out) throws Exception {
        Iterator<Tuple3<String, String, String>> iterator = input.iterator();
        StringBuilder sb = new StringBuilder();
        while (iterator.hasNext()) {
            Tuple3<String, String, String> next = iterator.next();
            sb.append(next.f0 + ".." + next.f1 + ".." + next.f2);
        }
//                window.
        out.collect(sb.toString());
    }
}); 

基於事件的滾動窗口

/**
* 滑動窗口:窗口可重疊
* 1、基於時間驅動
* 2、基於事件驅動
*/
WindowedStream<Tuple3<String, String, String>, String, GlobalWindow> countWindow = keybyed.countWindow(3,2);

SingleOutputStreamOperator<String> applyed = countWindow.apply(new WindowFunction<Tuple3<String, String, String>, String, String, GlobalWindow>() {
    @Override
    public void apply(String s, GlobalWindow window, Iterable<Tuple3<String, String, String>> input, Collector<String> out) throws Exception {
        Iterator<Tuple3<String, String, String>> iterator = input.iterator();
        StringBuilder sb = new StringBuilder();
        while (iterator.hasNext()) {
            Tuple3<String, String, String> next = iterator.next();
            sb.append(next.f0 + ".." + next.f1 + ".." + next.f2);
        }
//                window.
        out.collect(sb.toString());
    }
});

會話時間窗口

由一系列事件組合一個指定時間長度的timeout間隙組成,類似於web應用的session,也就是一段時間沒有接收到新數據就會生成新的窗口,在這種模式下,窗口的長度是可變的,每個窗口的開始和結束時間並不是確定的。我們可以設置定長的Session gap,也可以使用SessionWindowTimeGapExtractor動態地確定Session gap的長度。

val input: DataStream[T] = ...
// event-time session windows with static gap
input
    .keyBy(...)
    .window(EventTimeSessionWindows.withGap(Time.minutes(10)))
    .<window function>(...)
// event-time session windows with dynamic gap
input
    .keyBy(...)
    .window(EventTimeSessionWindows.withDynamicGap(new SessionWindowTimeGapExtractor[T] {
      override def extract(element: T): Long = {
        // determine and return session gap
      }
    }))
    .<window function>(...)
// processing-time session windows with static gap
input
    .keyBy(...)
    .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))
    .<window function>(...)
// processing-time session windows with dynamic gap
input
    .keyBy(...)
    .window(DynamicProcessingTimeSessionWindows.withDynamicGap(new SessionWindowTimeGapExtractor[T] {
      override def extract(element: T): Long = {
        // determine and return session gap
      }
    }))
    .<window function>(...)

窗口函數

在窗口劃分完畢後,就是要對窗口內的數據進行處理,一是增量計算對應reduceaggregate,二是全量計算對應process ,增量計算指的是窗口保存一份中間數據,每流入一個新元素,新元素與中間數據兩兩合一,生成新的中間數據,再保存到窗口中。全量計算指的是窗口先緩存該窗口所有元素,等到觸發條件後對窗口內的全量元素執行計算

參考

https://cloud.tencent.com/developer/article/1584926

吳邪,小三爺,混跡於後臺,大數據,人工智能領域的小菜鳥。
更多請關注
file

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章