概述
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();
}
}