首先结合flink四大基石去理解:时间 、状态 、检查点 、 窗口
其中窗口之所以这么重要是因为,我们知道flink是流批一体的、需要对数据进行统计计算的时候就用到了窗口。然后窗口也是一个逻辑的概念,实际上就是对数据在某个阶段做某种操作。经过规范后
窗口按时间划分:有滚动窗口和滑动窗口;
按照数量划分: 也有滚动窗口和滑动窗口;
其实还有一种就是会话窗口,但是用得不多,这边也不太了解。
(滚动窗口如果填两个参数,那么第二个参数是偏移量)
源码如下:
protected TumblingEventTimeWindows(long size, long offset, WindowStagger windowStagger) {
if (Math.abs(offset) >= size) {
throw new IllegalArgumentException(
"TumblingEventTimeWindows parameters must satisfy abs(offset) < size");
}
this.size = size;
this.globalOffset = offset;
this.windowStagger = windowStagger;
}
@Override
public Collection assignWindows(
Object element, long timestamp, WindowAssignerContext context) {
if (timestamp > Long.MIN_VALUE) {
if (staggerOffset == null) {
staggerOffset =
windowStagger.getStaggerOffset(context.getCurrentProcessingTime(), size);
}
// Long.MIN_VALUE is currently assigned when no timestamp is present
long start =
TimeWindow.getWindowStartWithOffset(
timestamp, (globalOffset + staggerOffset) % size, size);
return Collections.singletonList(new TimeWindow(start, start + size));
} else {
throw new RuntimeException(
"Record has Long.MIN_VALUE timestamp (= no timestamp marker). "
+ "Is the time characteristic set to 'ProcessingTime', or did you forget to call "
+ "'DataStream.assignTimestampsAndWatermarks(...)'?");
}
}
所以可以这样写就是每天凌晨八点开始计算。
windowAll(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8)))
另外窗口也分为 分组后的窗口 和 没有分组后使用的窗口
分别是 window 和 windowAll
有分组的话,我们可以想象就是很多个窗口分别计算结果,没有分组的话就是只有一个窗口。
根据Sql语句去理解的话就是,加和不加group by 聚合出来的结果是不一样的。
窗口组件assigner做的就是这种事情
窗口操作包含以下四个组件:
1、assigner已经说过了。
2、function 就是窗口中具体要做的事情,常用的有:
这些方法跟在定义好的window后面,比如
前两种方法使用比较方便,但是自定义方法最灵活,使用的比较多。
自定义方法适合做一些全量操作,因为如图,他可以将输入的数据收集成一个iterable,然后就可以对其进行遍历,然后再存集合,使用集合的各种特性和方法实现需求。
3、然后是窗口的触发器triger
触发器也分为四种:
CONTINUE: 什么都不做
FIRE:触发计算,
PURGE: 清除窗口中的所有数据
FIRE_AND_PURGE:触发计算并清除窗口中的所有数据
触发器的存在实现了窗口的按时关闭和计算。
4、最后是退出器evictor
我们在使用Flink window时,还可以指定一个Evictor,它是一个可选组件。Evictor可以在触发器触发后,它可以在WindowFunction执行之前或者之后移除一些元素。
然后关于窗口的其他操作比如:
1、可以通过allowedLateness延迟窗口关闭,如下能多保持五分钟
flatMapDS
.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))
.allowedLateness(Time.minutes(5))
2、通过sideOutputLateData可以获取到延迟的数据,然后我们可以使用侧输出流来获取侧输出结果数据。
然后通过.getSideOutput("上面设置好的存入标签"),收集到最晚的数据,然后保证数据不丢失。
flatMapDS
.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))
.allowedLateness(Time.minutes(5))
.sideOutputLateData(new OutputTag<>("isLate"));
.getSideOutput("isLate")
.print();
然后就是如果咱们的时间语义是“事件时间”的话 (当然一般使用的都是事件时间),那么就要在窗口操作之前先给数据加一个水位线了,其实就是打一个时间戳,使窗口的触发器能够触发计算。当然,如果时间语义是事件时间的话,不设水位线还要使用事件时间窗口操作那肯定是嘎嘎报错啊!