Flink 提供了非常完善的窗口机制,窗口就是从Streaming到batch的一个桥梁,是 Flink 最大的亮点之一(其他的亮点包括消息乱序处理,和 checkpoint 机制等)。
窗口的生命周期,就是创建和销毁。
每个窗口都会绑定一个触发器和一个执行函数。
经典的窗口划分为几类:时间窗口、事件窗口、会话窗口;其中时间窗口和事件窗口又可以划分为滚动窗口和滑动窗口;滚动窗口和滑动窗口的区别在于滚动窗口没有重叠,滑动窗口有重叠。
1.1 滚动窗口(Tumbling Window)
// Stream of (userId, buyCnt)
val buyCnts: DataStream[(Int, Int)] = ...val tumblingCnts: DataStream[(Int, Int)] = buyCnts
// key stream by userId
.keyBy(0)
// tumbling time window of 1 minute length
.timeWindow(Time.minutes(1))
// compute sum over buyCnt
.sum(1)
1.2 滑动窗口(Sliding Window)
val slidingCnts: DataStream[(Int, Int)] = buyCnts
.keyBy(0)
// sliding time window of 1 minute length and 30 secs trigger interval
.timeWindow(Time.minutes(1), Time.seconds(30))
.sum(1)
2.1 滚动窗口(Tumbling Window)
// Stream of (userId, buyCnts)
val buyCnts: DataStream[(Int, Int)] = ...val tumblingCnts: DataStream[(Int, Int)] = buyCnts
// key stream by sensorId
.keyBy(0)
// tumbling count window of 100 elements size
.countWindow(100)
// compute the buyCnt sum
.sum(1)
2.2 滑动窗口(Sliding Window)
val slidingCnts: DataStream[(Int, Int)] = vehicleCnts
.keyBy(0)
// sliding count window of 100 elements size and 10 elements trigger interval
.countWindow(100, 10)
.sum(1)
// Stream of (userId, buyCnts)
val buyCnts: DataStream[(Int, Int)] = ...
val sessionCnts: DataStream[(Int, Int)] = vehicleCnts
.keyBy(0)
// session window based on a 30 seconds session gap interval
.window(ProcessingTimeSessionWindows.withGap(Time.seconds(30)))
.sum(1)
窗口函数主要分为两种,一种是增量计算,如reduce
和aggregate
,一种是全量计算,如process
。增量计算指的是窗口保存一份中间数据,每流入一个新元素,新元素与中间数据两两合一,生成新的中间数据,再保存到窗口中。全量计算指的是窗口先缓存该窗口所有元素,等到触发条件后对窗口内的全量元素执行计算。
使用reduce算子时,我们要重写一个ReduceFunction。ReduceFunction接受两个相同类型的输入,生成一个输出,即两两合一地进行汇总操作,生成一个同类型的新元素。在窗口上进行reduce的原理与之类似,只不过多了一个窗口状态数据,这个状态数据的数据类型和输入的数据类型是一致的,是之前两两计算的中间结果数据。当数据流中的新元素流入后,ReduceFunction将中间结果和新流入数据两两合一,生成新的数据替换之前的状态数据。
AggregateFunction
也是一种增量计算窗口函数,也只保存了一个中间状态数据,但AggregateFunction
使用起来更复杂一些。
public interface AggregateFunction
extends Function, Serializable { // 在一次新的aggregate发起时,创建一个新的Accumulator,Accumulator是我们所说的中间状态数据,简称ACC
// 这个函数一般在初始化时调用
ACC createAccumulator();// 当一个新元素流入时,将新元素与状态数据ACC合并,返回状态数据ACC
ACC add(IN value, ACC accumulator);
// 将两个ACC合并
ACC merge(ACC a, ACC b);// 将中间数据转成结果数据
OUT getResult(ACC accumulator);}
输入类型是IN,输出类型是OUT,中间状态数据是ACC,这样复杂的设计主要是为了解决输入类型、中间状态和输出类型不一致的问题,同时ACC可以自定义,我们可以在ACC里构建我们想要的数据结构。比如我们要计算一个窗口内某个字段的平均值,那么ACC中要保存总和以及个数。
与前两种方法不同,ProcessWindowFunction
要对窗口内的全量数据都缓存。在Flink所有API中,process
算子以及其对应的函数是最底层的实现,使用这些函数能够访问一些更加底层的数据,比如,直接操作状态等。
ProcessWindowFunction相比AggregateFunction和ReduceFunction的应用场景更广,能解决的问题也更复杂。但ProcessWindowFunction需要将窗口中所有元素作为状态存储起来,这将占用大量的存储资源,尤其是在数据量大窗口多的场景下,使用不慎可能导致整个程序宕机。
当我们既想访问窗口里的元数据,又不想缓存窗口里的所有数据时,可以将ProcessWindowFunction与增量计算函数相reduce和aggregate结合。对于一个窗口来说,Flink先增量计算,窗口关闭前,将增量计算结果发送给ProcessWindowFunction作为输入再进行处理。
参考
https://zhuanlan.zhihu.com/p/102325190