概述
window可以將flink處理的無限stream流切分成有限流,進行時間段內數據的計算,它是有限流處理的核心組件。window對流的切分可以是基於時間的(Time Window),也可以是基於數據的(Count Window)。
主要的操作如下:
注:例子中的kafkaSource是一個DataStream對象
keyed windows operator
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oI325lYA-1574937195558)(/Users/jiang.li/Library/Application Support/typora-user-images/image-20191127095351226.png)]
Non-keyed windows operator
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cLeVrcR7-1574937195566)(/Users/jiang.li/Library/Application Support/typora-user-images/image-20191127095457046.png)]
這裏可以看到,除了沒有進行keyBy操作以及之後的window操作外,其他的操作都是一樣的。略作解釋:keyBy操作之後生成的KeyedStream<String,Tuple> 是 DataStream的子類,keyedStream的屬性和方法如下圖:
DataStream的屬性和方法如下圖:
可以對比出,keyedStream擴展了關於window的方法,countWindow(countWindowAll),timeWindow(timeWindowAll),window(WindowAll),用來表示根據key(水平切分)和window(豎直切分)劃分好的單元格。至於三個window之間的關係,window需要傳入一個WindowAssigner來進行窗口的創建,countWindow是將WindowAssigner指定爲GlobalWindows同時藉助trigger和evictor操作來實現的,如下圖
timeWindow是根據時間語義將WindowAssigner指定爲TumblingProcessingTimeWindows/TumblingEventTimeWindows 或者 SlidingProcessingTimeWindows/SlidingEventTimeWindows來實現的
同時keyedStream重寫了addSink,process,setConnectionType方法,這裏就不展開了。
其他的這裏也暫時不展開。
關於windowAssigner
具體windowAssigner的子類見下圖
它負責將每條輸入數據分發到正確的window中。Flink 提供了幾種通用的 WindowAssigner:tumbling window(窗口間的元素無重複),sliding window(窗口間的元素可能重複),session window 以及 global window。如果需要自己定製數據分發策略,則可以實現一個 class,繼承自 WindowAssigner。
tumbling window(窗口間的元素無重複)
sliding window(窗口間的元素可能重複)
Session Window(一個會話一個window)
Global Window(全部數據都在一個window)
關於evictor
evictor 主要用於做一些數據的自定義操作,可以在執行用戶代碼之前(evicBefore方法),也可以在執行用戶代碼之後(evicAfter方法),是可選的方法,如果用戶不選擇,則默認沒有。具體的繼承關係如下圖
* CountEvictor 保留指定數量的元素,需要指定是用戶代碼執行之前保留還是執行之後保留
* DeltaEvictor 通過執行用戶給定的 DeltaFunction 以及預設的 threshold,判斷是否刪除一個元素。
* TimeEvictor設定一個閾值 interval,刪除所有不再 max_ts – interval 範圍內的元素,其中 max_ts 是窗口內時間戳的最大值。
關於trigger
trigger 用來判斷一個窗口是否需要被觸發,每個 WindowAssigner 都自帶一個默認的 trigger,trigger繼承關係如下:
如果默認的 trigger 不能滿足你的需求,則可以自定義一個類,繼承自 Trigger 即可,我們詳細描述下 Trigger 的接口以及含義:
- onElement() 每次往 window 增加一個元素的時候都會觸發
* onEventTime() 當 event-time timer 被觸發的時候會調用
* onProcessingTime() 當 processing-time timer 被觸發的時候會調用
* onMerge() 對兩個 trigger 的 state 進行 merge 操作
* clear() window 銷燬的時候被調用
上面的接口中前三個會返回一個 TriggerResult,TriggerResult 有如下幾種可能的選擇:
* CONTINUE 不做任何事情
* FIRE 觸發 window
* PURGE 清空整個 window 的元素並銷燬窗口
* FIRE_AND_PURGE 觸發窗口,然後銷燬窗口
這裏以org.apache.flink.streaming.api.windowing.triggers.ContinuousEventTimeTrigger爲例進行剖析:
首先在創建對象的時候
//觸發的間隔時間
private final long interval;
//創建狀態變量描述
/** When merging we take the lowest of all fire timestamps as the new fire timestamp. */
private final ReducingStateDescriptor<Long> stateDesc =
new ReducingStateDescriptor<>("fire-time", new Min(), LongSerializer.INSTANCE);
//構造方法
private ContinuousEventTimeTrigger(long interval) {
this.interval = interval;
}
剛開始向window中增加元素的時候會觸發onElement方法
//每次往 window 增加一個元素的時候都會觸發,但是不做任何事情
@Override
public TriggerResult onElement(Object element, long timestamp, W window, TriggerContext ctx) throws Exception {
if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
// if the watermark is already past the window fire immediately
return TriggerResult.FIRE;
} else {
//註冊定時器
ctx.registerEventTimeTimer(window.maxTimestamp());
}
//獲取當前分區的狀態
ReducingState<Long> fireTimestamp = ctx.getPartitionedState(stateDesc);
//判斷狀態中的值
if (fireTimestamp.get() == null) {
//上次觸發時間/開始放入元素的時間
long start = timestamp - (timestamp % interval);
//下次啓動時間
long nextFireTimestamp = start + interval;
//註冊定時器
ctx.registerEventTimeTimer(nextFireTimestamp);
//添加下次啓動時間,不停的添加會導致存在多個觸發時間,之後會進行merge操作
fireTimestamp.add(nextFireTimestamp);
}
return TriggerResult.CONTINUE;
}
當 event-time timer 被觸發的時候會調用
//當 event-time timer 被觸發的時候會調用
//time表示當前時間
@Override
public TriggerResult onEventTime(long time, W window, TriggerContext ctx) throws Exception {
if (time == window.maxTimestamp()){
//如果最大時間到了就會觸發整個window,且不回進行下次觸發
return TriggerResult.FIRE;
}
ReducingState<Long> fireTimestampState = ctx.getPartitionedState(stateDesc);
Long fireTimestamp = fireTimestampState.get();
if (fireTimestamp != null && fireTimestamp == time) {
//如果fireTimestamp不爲空且是當前時間,那麼觸發window
//清理當前狀態
fireTimestampState.clear();
fireTimestampState.add(time + interval);
//註冊定時啓動器,下次調用此方式的時間
ctx.registerEventTimeTimer(time + interval);
return TriggerResult.FIRE;
}
return TriggerResult.CONTINUE;
}
通過同時存在好多觸發器,會進行以下操作
@Override
public boolean canMerge() {
return true;
}
//對多個 trigger 的 state 進行 merge 操作
@Override
public void onMerge(W window, OnMergeContext ctx) throws Exception {
//將當前分區的state進行merge操作
ctx.mergePartitionedState(stateDesc);
//獲取下次觸發的時間
Long nextFireTimestamp = ctx.getPartitionedState(stateDesc).get();
if (nextFireTimestamp != null) {
ctx.registerEventTimeTimer(nextFireTimestamp);
}
}
window 銷燬的時候被調用
//window 銷燬的時候被調用
@Override
public void clear(W window, TriggerContext ctx) throws Exception {
ReducingState<Long> fireTimestamp = ctx.getPartitionedState(stateDesc);
Long timestamp = fireTimestamp.get();
if (timestamp != null) {
ctx.deleteEventTimeTimer(timestamp);
fireTimestamp.clear();
}
}