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有
reduce
、aggregate
、process
滾動窗口
基於時間驅動
將數據依據固定的窗口長度對數據進行切分,滾動窗口下窗口之間之間不重疊,且窗口長度是固定的。我們可以用TumblingEventTimeWindows
和TumblingProcessingTimeWindows
創建一個基於Event Time或Processing Time的滾動時間窗口。窗口的長度可以用org.apache.flink.streaming.api.windowing.time.Time
中的seconds
、minutes
、hours
和days
來設置。
//關鍵處理案例
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>(...)
窗口函數
在窗口劃分完畢後,就是要對窗口內的數據進行處理,一是增量計算對應reduce
和aggregate
,二是全量計算對應process
,增量計算指的是窗口保存一份中間數據,每流入一個新元素,新元素與中間數據兩兩合一,生成新的中間數據,再保存到窗口中。全量計算指的是窗口先緩存該窗口所有元素,等到觸發條件後對窗口內的全量元素執行計算
參考
https://cloud.tencent.com/developer/article/1584926
吳邪,小三爺,混跡於後臺,大數據,人工智能領域的小菜鳥。
更多請關注