一、Window的分类
什么是window?
Flink是一个流处理计算框架,它底层基于流处理引擎,实现了批处理与流处理,在流处理中,为了处理无限的数据集,使用window将无限的数据流切分多个有限的数据块进行计算。
我们按照具体的业务需求将window划分为KeyedWindow
以及Non-KeyedWindow
,如果是KeyedWindow
那么它会有多个并行度来计算窗口中的数据,对于KeyedWindow
的并行度则为1。
以time window为例,按照key分组与不分组的window划分如下图所示:
在Keyed Window
和Non-Keyed Window
中又分为Count-based window
根据元素个数对数据流进行分组切分,Time-based window
根据时间对数据流进行分组切分。
Count-based window
又有以下分类:
- Tumbling CountWindow
- Sliding CountWindow
Time-based window
有以下分类:
- Tumbling Window
- Sliding Window
- Session Window
对于Keyed Window
还有Global Window
,即不划分窗口,所以使用该窗口必须定义触发器。
对于每一个窗口的大小是[start,end),即前闭后开区间。
Tumbling Window
将数据依据固定的窗口长度对数据进行切片,窗口的长度固定,event没有 重叠。
该窗口的设置可以设置窗口的大小,通过设置时间,时,分,秒等,也可以通过offset来指定偏移量。比如窗口设置为一天,单并不需要从整点开始,即提前8小时开始创建窗口。
DataStream<T> input = ...;
// tumbling event-time windows
input
.keyBy(<key selector>)
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.<windowed transformation>(<window function>);
// tumbling processing-time windows
input
.keyBy(<key selector>)
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.<windowed transformation>(<window function>);
// daily tumbling event-time windows offset by -8 hours.
input
.keyBy(<key selector>)
.window(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8)))
.<windowed transformation>(<window function>);
Sliding Window
滑动窗口由固定的窗口长度和滑动间隔组成,event有重叠,窗口大小固定。
该窗口可以设置窗口的大小,以及滑动的距离,也可以设置offset设置窗口的偏移量。
DataStream<T> input = ...;
// sliding event-time windows
input
.keyBy(<key selector>)
.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.<windowed transformation>(<window function>);
// sliding processing-time windows
input
.keyBy(<key selector>)
.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.<windowed transformation>(<window function>);
// sliding processing-time windows offset by -8 hours
input
.keyBy(<key selector>)
.window(SlidingProcessingTimeWindows.of(Time.hours(12), Time.hours(1), Time.hours(-8)))
.<windowed transformation>(<window function>);
Session Window
类似于web应用中的session,即一段时间没有接受到新数据就会生成新的窗口,特点是时间无对齐,event不重叠,没有固定的开始时间以及结束时间,只需指定session gap即可,常用于线上用户行为分析。
在session window设置gap时,可以设置固定gap和动态gap,如要实现动态gap,需要实现SessionWindowTimeGapExtractor
接口。使用方式如下:
DataStream<T> input = ...;
// event-time session windows with static gap
input
.keyBy(<key selector>)
.window(EventTimeSessionWindows.withGap(Time.minutes(10)))
.<windowed transformation>(<window function>);
// event-time session windows with dynamic gap
input
.keyBy(<key selector>)
.window(EventTimeSessionWindows.withDynamicGap((element) -> {
// determine and return session gap
}))
.<windowed transformation>(<window function>);
// processing-time session windows with static gap
input
.keyBy(<key selector>)
.window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))
.<windowed transformation>(<window function>);
// processing-time session windows with dynamic gap
input
.keyBy(<key selector>)
.window(ProcessingTimeSessionWindows.withDynamicGap((element) -> {
// determine and return session gap
}))
.<windowed transformation>(<window function>);
Global Window
有相同key的所有元素分配给相同的单个全局窗口,使用时必须要自定义触发器,注意:使用在Keyed Stream中。
总结
除了上述的方法使用window外,还可以使用下面的方法:
Keyed Windows
- Tumbling time window:.timeWindow(Time.seconds(30))
- Sliding time window:.timeWindow(Time.seconds(30), Time.seconds(10))
- Tumbling count window:.countWindow(1000)
- Sliding count window:.countWindow(1000, 10)
- Session window:.window(SessionWindows.withGap(Time.minutes(10)))
Non-Keyed Windows
- Tumbling time window:.timeWindowAll(Time.seconds(30))
- Sliding time window:.timeWindowAll(Time.seconds(30), Time.seconds(10))
- Tumbling count window:.countWindowAll(1000)
- Sliding count window:.countWindowAll(1000, 10)
- Session window:.window(SessionWindows.withGap(Time.minutes(10)))
二、触发器Trigger&驱逐器Evictor
触发器
触发器决定了一个窗口何时可以被窗口函数处理(条件满足时触发并发出信号)。每一个window都有一个默认的触发器,当默认的触发器不满足你的需求时,可以通过调用trigger(...)
来自定义一个触发器来使用。
如果要自定义触发器,只需要实现抽象类Trigger即可,它主要有以下几个抽象方法:
@PublicEvolving
public abstract class Trigger<T, W extends Window> implements Serializable {
private static final long serialVersionUID = -4104633972991191369L;
//每个元素被添加到窗口时调用
public abstract TriggerResult onElement(T element, long timestamp, W window, TriggerContext ctx) throws Exception;
//当一个已注册的处理时间计时器启动时调用
public abstract TriggerResult onProcessingTime(long time, W window, TriggerContext ctx) throws Exception;
//当一个已注册的事件时间计时器启动时调用
public abstract TriggerResult onEventTime(long time, W window, TriggerContext ctx) throws Exception;
//是否支持合并
public boolean canMerge() {
return false;
}
//与状态性触发器相关,当使用session window时,两个触发器对应的窗口合并时,合并两个触发器的状态。
public void onMerge(W window, OnMergeContext ctx) throws Exception {
throw new UnsupportedOperationException("This trigger does not support merging.");
}
//相应窗口被清除时触发
public abstract void clear(W window, TriggerContext ctx) throws Exception;
可以看到上述方法通过返回一个TriggerResult
来决定如何对其调用的事件进行处理,主要有以下处理方式:
public enum TriggerResult {
//什么也不做
CONTINUE(false, false),
//触发计算并随后清除窗口中的元素
FIRE_AND_PURGE(true, true),
//触发计算
FIRE(true, false),
//清除窗口中的数据
PURGE(false, true);
......
}
对于每一个window默认的有一个触发器,比如所有基于EventTime的Window都有一个EventTimeTrigger
作为触发器,GlobalWindow
的默认触发器是NeverTrigger
,即永不触发,所以使用时必须要自定义触发器。当我们使用trigger()
来指定触发器时,将覆盖默认的触发器。在Flink中有一些内置的Trigger
触发器:
- EventTimeTrigger:根据由watermark衡量的Event Time进度来触发
- ProcessingTimeTrigger:根据处理时间来触发
- CountTrigger :一旦窗口中的元素个数超出了给定的限制就会触发
- PurgingTrigger :接受另一个触发器作为参数,并将其转换为一个purging触发器(当嵌套触发器触发时,将返回FIRE_AND_PURGE类型的TriggerResult)
驱逐器
驱逐器是可选操作,默认是没有的,它能够在窗口触发器前后删除一些无用的元素,类似于filter的功能。如果自定义Evictor
,需要实现Evictor
这个接口,它主要有 以下两个方法:
public interface Evictor<T, W extends Window> extends Serializable {
//在窗口函数之前被应用
void evictBefore(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);
//在窗口函数之后被应用
void evictAfter(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);
......
}
Flink不能保证窗口中元素的顺序。这意味着,尽管驱逐者从窗口的开头删除元素,但这些元素不一定是最先或最后到达的元素。在Flink中,还有一些内置的驱逐器:
- CountEvitor:在窗口中保持用户指定数量的元素,并在窗口的开始处丢弃多余的元素。
- DeltaEvitor:通过一个DeltaFunction和一个阈值,计算窗口缓存中最后一个元素和剩余的所有元素的delta值,并清除delta值大于或者等于阈值的元素。
- TimeEvitor:使用一个interval(毫秒数)作为参数,对于一个给定的窗口,它会找出元素中的最大时间戳max_ts,并清除时间戳小于max_tx - interval的元素。