flink 窗口源码分析
本文主要围绕 WindowOperator类的三个方法来具体展开 :
一 . processElement(StreamRecord element) 在记录进入窗口窗子的时候被调用
二 . onEventTime(InternalTimer<K, W> timer) 在更新watermark 时候被调用
三 . onProcessingTime(InternalTimer<K, W> timer) 通过 StreamSource类不断地往下调用,就是只要任务启动了,就一直调用
在看这三个方法的具体实现之前,先了解几个问题
一. 窗口类型
一. 数据如何分配窗口
二. 窗口触发器
窗口类型
如下图,flink 有两种窗口,一种全局窗口,一种时间窗口
时间窗口
成员变量 :两个成员变量,为窗口的开始和结束时间
方法 :
getStart() :获取窗口开始时间
getEnd(): 获取窗口结束时间
maxTimestamp () : 获取仍然属于此窗口的最大时间戳
intersects(TimeWindow other): 判断和其他窗口是否有重合
cover(TimeWindow other) :返回和其他窗口的并集
mergeWindows(Collection windows, MergingWindowAssigner.MergeCallback c) :合并窗口,例如多个 session合并
全局窗口
是单例的,在不使用时间窗口的前提下,所有数据都数据改窗口。
Assinger
Assinger类型
对于时间窗口,每个assiner都分为eventTime和processingTime两类
windowAssinger | 功能 |
---|---|
DynamicSessionWindows | 可以根据record中的某个字段动态指定 session 的gap |
SessionWindows | session 的gap是固定的,不可以动态指定(相比上面的,功能没有那么强大) |
SlidingWindows | 滑动窗口,指定窗口大小和滑动间隔 |
TumblingWindows | 翻滚窗口,指定窗口大小 |
如上图:主要有 DynamicsessionWindowAssigner,ssessionWindowsAssigner,GlobalWindowsAssigner,SlidingWindowsAssigner,TumblingWindowsAssigner。 其中每个Assigner都有evnetTime和processTime两种实现。
sessionWindows都继承了 MergingWindowAssigner,表明该Assigner是可以合并窗口
接下来说一下三种WindowsAssigner的assignWindows方法,以eventTime举例,同processTime一样,只不过传入的timestamp 不同而已!
TumblingEventTimeWindows
offset 时区偏移量,size 是窗口大小
assignWindows 返回属于该元素的一个窗口
计算窗口开始时间如上图,假设窗口当前时间为23,窗口大小为5,offset 为0,则窗口为[20,25],由公示可以看出窗口的开始时间和结束时间一定是 窗口大小的倍数。
所以说,flink 任务开始时的第一个窗口的数据可能是不完整的,好比这个例子,相当于只有第一个窗口只有[23,25]的数据。
SlidingEventTimeWindows
假设 当前元素时间为 24 ,窗口大小为4,slide为2,那么可以计算出该元素属于[20,24],[22,26],[24,28]这三个窗口 。
EventTimeSessionWindows
这就很简单啦,开始时间为当前时间,结束时间为当前+session超时时间 。
当然,这不是某个元素最终的窗口大小,因为这个窗口是可以合并的。后面会讲。
Tigger
trigger类型
trigger | 作用 |
---|---|
ContinuousEventTimeTrigger | 在窗口大小范围内,根据event time的固定间隔多次触发。比如一个10s的翻滚窗口,用这个tirgger设置2s的间隔,那么在2s,4s,6s,8s,10s都会触发一次,知道窗口结束。 |
ContinuousProcessingTimeTrigger | 在窗口大小范围内,根据processing time的固定间隔多次触发。比如一个10s的翻滚窗口,用这个tirgger设置2s的间隔,那么在2s,4s,6s,8s,10s都会触发一次,知道窗口结束。 |
CountTrigger | 在窗口大小范围内,根据element 条数多次触发。 |
DeltaTrigger | 多次触发,当前element的某个字段和上次触发的element的某个字段做delta计算,超过threshold就触发。 |
EventTimeTrigger | 一次触发,当watermark大于窗口结束时间就触发 |
ProcessingTimeTrigger | 一次触发,当machine time大于窗口结束时间就触发 |
PurgingTrigger | tirgger warpper,当nester trigger触发是,会清空当前窗口的状态。 |
其中ContinuousTrigger ,deltaTrigger,purgingTrigger是比较高级的tirgger,可以用来实现一些复杂的场景。
作用就是判断当前窗口是否触发的一个东西
ProcessingTimeTrigger
public class ProcessingTimeTrigger extends Trigger<Object, TimeWindow> {
private static final long serialVersionUID = 1L;
private ProcessingTimeTrigger() {
}
//在元素进入window operator的时候被调用,直接注册一个窗口结束时间的定时器,
// 通过internalTimerService定时调度,调度的时候调用 本类中onProcessingTime 方法直接触发.
@Override
public TriggerResult onElement(Object element, long timestamp, TimeWindow window, TriggerContext ctx) {
ctx.registerProcessingTimeTimer(window.maxTimestamp());
return TriggerResult.CONTINUE;
}
//这个可以忽略
@Override
public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {
return TriggerResult.CONTINUE;
}
//通过internalTimerService定时调度,直接fire窗口
@Override
public TriggerResult onProcessingTime(long time, TimeWindow window, TriggerContext ctx) {
return TriggerResult.FIRE;
}
ProcessingTimeTrigger
@PublicEvolving
public class EventTimeTrigger extends Trigger<Object, TimeWindow> {
private static final long serialVersionUID = 1L;
private EventTimeTrigger() {
}
// 在元素进入window operator的时候被调用
// 如果 延迟数据到达,且数据在最大允许延迟之内,窗口没有被清除,则会触发计算
// 否则 非延迟数据,则通过internalTimerService定时调度,调度的时候调用 本类中onEventTime 方法直接触发
@Override
public TriggerResult onElement(Object element, long timestamp, TimeWindow 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());
return TriggerResult.CONTINUE;
}
}
// Time 也就是waterMarker,等于窗口结束时间则触发,否则不触发
@Override
public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) {
return time