轉載原文: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
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