Flink Windows窗口簡介和使用

轉載原文:https://blog.csdn.net/lmalds/article/details/51604501

Apache Flink–DataStream–Window

什麼是Window?有哪些用途? 
下面我們結合一個現實的例子來說明。

我們先提出一個問題:統計經過某紅綠燈的汽車數量之和? 
假設在一個紅綠燈處,我們每隔15秒統計一次通過此紅綠燈的汽車數量,如下圖: 
這裏寫圖片描述 
可以把汽車的經過看成一個流,無窮的流,不斷有汽車經過此紅綠燈,因此無法統計總共的汽車數量。但是,我們可以換一種思路,每隔15秒,我們都將與上一次的結果進行sum操作(滑動聚合),如下: 
這裏寫圖片描述 
這個結果似乎還是無法回答我們的問題,根本原因在於流是無界的,我們不能限制流,但可以在有一個有界的範圍內處理無界的流數據。

因此,我們需要換一個問題的提法:每分鐘經過某紅綠燈的汽車數量之和? 
這個問題,就相當於一個定義了一個Window(窗口),window的界限是1分鐘,且每分鐘內的數據互不干擾,因此也可以稱爲翻滾(不重合)窗口,如下圖: 
這裏寫圖片描述 
第一分鐘的數量爲8,第二分鐘是22,第三分鐘是27。。。這樣,1個小時內會有60個window。

再考慮一種情況,每30秒統計一次過去1分鐘的汽車數量之和: 
這裏寫圖片描述 
此時,window出現了重合。這樣,1個小時內會有120個window。

擴展一下,我們可以在某個地區,收集每一個紅綠燈處汽車經過的數量,然後每個紅綠燈處都做一次基於1分鐘的window統計,即並行處理: 
這裏寫圖片描述

通常來講,Window就是用來對一個無限的流設置一個有限的集合,在有界的數據集上進行操作的一種機制。window又可以分爲基於時間(Time-based)的window以及基於數量(Count-based)的window。

Flink DataStream API提供了Time和Count的window,同時增加了基於Session的window。同時,由於某些特殊的需要,DataStream API也提供了定製化的window操作,供用戶自定義window。

下面,主要介紹Time-Based window以及Count-Based window,以及自定義的window操作,Session-Based Window操作將會在後續的文章中講到。

1、Time-Based Window 
1.1、Tumbling window(翻滾) 
此處的window要在keyed Stream上應用window操作,當輸入1個參數時,代表Tumbling window操作,每分鐘統計一次,此處用scala語言實現:

// Stream of (sensorId, carCnt)
val vehicleCnts: DataStream[(Int, Int)] = ...

val tumblingCnts: DataStream[(Int, Int)] = vehicleCnts
  // key stream by sensorId
  .keyBy(0) 
  // tumbling time window of 1 minute length
  .timeWindow(Time.minutes(1))
  // compute sum over carCnt
  .sum(1) 

1.2、Sliding window(滑動) 
當輸入2個參數時,代表滑動窗口,每隔30秒統計過去1分鐘的數量:

val slidingCnts: DataStream[(Int, Int)] = vehicleCnts
  .keyBy(0) 
  // sliding time window of 1 minute length and 30 secs trigger interval
  .timeWindow(Time.minutes(1), Time.seconds(30))
  .sum(1)

Note:Flink中的Time概念共有3個,即Processing Time(wall clock),Event Time以及Ingestion Time,我會在後續的文章中講到。(國內目前wuchong以及Vinoyang都有講過).

2、Count-Based Window 
2.1、Tumbling Window 
和Time-Based一樣,Count-based window同樣支持翻滾與滑動窗口,即在Keyed Stream上,統計每100個元素的數量之和:

// Stream of (sensorId, carCnt)
val vehicleCnts: DataStream[(Int, Int)] = ...

val tumblingCnts: DataStream[(Int, Int)] = vehicleCnts
  // key stream by sensorId
  .keyBy(0)
  // tumbling count window of 100 elements size
  .countWindow(100)
  // compute the carCnt sum 
  .sum(1)

2.2、Sliding Window 
每10個元素統計過去100個元素的數量之和:

val slidingCnts: DataStream[(Int, Int)] = vehicleCnts
  .keyBy(0)
  // sliding count window of 100 elements size and 10 elements trigger interval
  .countWindow(100, 10)
  .sum(1)

3、Advanced Window(自定義window) 
自定義的Window需要指定3個function。 
3.1、Window Assigner:負責將元素分配到不同的window。

// create windowed stream using a WindowAssigner
var windowed: WindowedStream[IN, KEY, WINDOW] = keyed
  .window(myAssigner: WindowAssigner[IN, WINDOW])

WindowAPI提供了自定義的WindowAssigner接口,我們可以實現WindowAssigner的public abstract Collection<W> assignWindows(T element, long timestamp)方法。同時,對於基於Count的window而言,默認採用了GlobalWindow的window assigner,例如:

keyValue.window(GlobalWindows.create())

3.2、Trigger 
Trigger即觸發器,定義何時或什麼情況下Fire一個window。 
我們可以複寫一個trigger方法來替換WindowAssigner中的trigger,例如:

// override the default trigger of the WindowAssigner
windowed = windowed
  .trigger(myTrigger: Trigger[IN, WINDOW])

對於CountWindow,我們可以直接使用已經定義好的Trigger:CountTrigger

trigger(CountTrigger.of(2))

3.3、Evictor(可選) 
驅逐者,即保留上一window留下的某些元素。

// specify an optional evictor
windowed = windowed
  .evictor(myEvictor: Evictor[IN, WINDOW])

Note:最簡單的情況,如果業務不是特別複雜,僅僅是基於Time和Count,我們其實可以用系統定義好的WindowAssigner以及Trigger和Evictor來實現不同的組合: 
例如:基於Event Time,每5秒內的數據爲界,以每秒的滑動窗口速度進行operator操作,但是,當且僅當5秒內的元素數達到100時,才觸發窗口,觸發時保留上個窗口的10個元素。

keyedStream
    .window(SlidingEventTimeWindows.of(Time.seconds(5), Time.seconds(1))
    .trigger(CountTrigger.of(100))
    .evictor(CountEvictor.of(10));
val countWindowWithoutPurge = keyValue.window(GlobalWindows.create()).
      trigger(CountTrigger.of(2))

 

最後,給出一個完整的例子,說明Window的用法:

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.GlobalWindows
import org.apache.flink.streaming.api.windowing.triggers.{CountTrigger, PurgingTrigger}
import org.apache.flink.streaming.api.windowing.windows.GlobalWindow

object Window {
  def main(args: Array[String]) {

    // set up the execution environment
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val source = env.socketTextStream("localhost",9000)

    val values = source.flatMap(value => value.split("\\s+")).map(value => (value,1))

    val keyValue = values.keyBy(0)

    // define the count window without purge

    val countWindowWithoutPurge = keyValue.window(GlobalWindows.create()).
      trigger(CountTrigger.of(2))


    val countWindowWithPurge = keyValue.window(GlobalWindows.create()).
      trigger(PurgingTrigger.of(CountTrigger.of[GlobalWindow](2)))

    countWindowWithoutPurge.sum(1).print()

    countWindowWithPurge.sum(1).print()

    env.execute()

    // execute program
    env.execute("Flink Scala API Skeleton")
  }
}

1]: http://flink.apache.org/news/2015/12/04/Introducing-windows.html

2]: https://ci.apache.org/projects/flink/flink-docs-release-1.0/apis/streaming/windows.html#advanced-window-constructs

3]: http://blog.madhukaraphatak.com/introduction-to-flink-streaming-part-6/

4]: https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101

5]: http://data-artisans.com/how-apache-flink-enables-new-streaming-applications-part-1/

6]: http://dataartisans.github.io/flink-training/exercises/popularPlaces.html

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