前言
窗口是flink最重要的几个部分之一, 如何对流计算的大量数据进行聚合处理以及控制流计算中发生的大量数据延时,或者数据提前来到导致的计算错误,flink使用window(窗口)给出了一份非常圆满的答案。
对于窗口的介绍
flink 的底层架构设计上其实是没有批处理(batch)的概念,在flink看来,批处理就是对于流处流(stream)一种特殊处理。但是在实际的业务情况中,经常有需要对于一段范围内的数据进行聚合处理的需要:比如我们需要统计刚刚的十分钟内到底有多少客户进行了抢购,抢劵。在这种情况下,我们可以使用窗口对于十分钟内的数据进行聚合和计算,flink的窗口在大部分的情况下都可以完美的满足聚合计算的需求。 窗口可以是时间单位的(time window),也可以是数据量作为单位的(count window)。窗口可以使用聚合方式来区分:滚动窗口tumbling windoe(无重叠),滑动窗口 sliding window(有重叠),和会话窗口 session window(以不活动间隙为间隔)。这里会分别对这三种窗口给予介绍和代码的解析。
Time window
time window 是依靠时间对数据流进行分组处理的,我们可以知道flink有三种时间的概念: eventTime(事件发生的事件), processingTime(事件被处理的时间) 和ingestionTime(事件进入流处理系统的时间 。
在org.apache.flink.streaming.api.operators.SourceContext
中可以看到对于三种时间的不同定义
public static <OUT> SourceFunction.SourceContext<OUT> getSourceContext() {
final SourceFunction.SourceContext<OUT> ctx;
switch (timeCharacteristic) {
case EventTime:
ctx =
new ManualWatermarkContext<>(
output,
processingTimeService,
checkpointLock,
idleTimeout,
emitProgressiveWatermarks);
break;
case IngestionTime:
Preconditions.checkState(
emitProgressiveWatermarks,
"Ingestion time is not available when emitting progressive watermarks "
+ "is disabled.");
ctx =
new AutomaticWatermarkContext<>(
output,
watermarkInterval,
processingTimeService,
checkpointLock,
idleTimeout);
break;
case ProcessingTime:
ctx = new NonTimestampContext<>(checkpointLock, output);
break;
default:
throw new IllegalArgumentException(String.valueOf(timeCharacteristic));
}
return new SwitchingOnClose<>(ctx);
}
三种时间对于flink 的watermark以及消息乱序的影响非常重大,但是因为时间和窗口在flink是解偶的,这里不做更多的讨论。
TumblingProcessingTimeWindows
Tumblingwindow 也就是滚动窗口,有两种实现方式,一种是按时间,比如5分钟,收集5分钟内所有到达窗口的数据然后计算,或者按数量,比如收集一百条数据,然后对这一百条数据进行计算。滚动窗口和其他窗口最大的区别是,窗口之间互相没有重叠,也没有交集的元素,所以滚动窗口的逻辑是最简单而又清晰的。
还是看之前的实例代码:
DataStream<SensorReading> avgTemp = sensorData
// 转化温度的格式
.map( r -> new SensorReading(r.id, r.timestamp, (r.temperature - 32) * (5.0 / 9.0)))
// 根据传感器的id分组
.keyBy(r -> r.id)
// 利用滚动窗口收集每5秒内的信息
.window( TumblingEventTimeWindows.of(Time.seconds(5)))
// 用自定义的方法实现计算平均温度
.apply(new TemperatureAverager());
复制代码
可以清晰的看到:在代码中滚动窗口的作用一:收集5秒内所有的数据 ,二: 汇总以后输出一个每5秒滚动的结果。在实例代码的业务上就是在屏幕内打印所有的传感器的温度值计算出来的平均温度,最后输出一个实时的准确的温度。
TumblingProcessingTimeWindows分为两种TumblingEventTimeWindows
和 TumblingProcessingTimeWindows
,这里首先来看TumblingEventTimeWindows
public class TumblingEventTimeWindows extends WindowAssigner<Object, TimeWindow> {
private static final long serialVersionUID = 1L;
private final long size;
private final long globalOffset;
private Long staggerOffset = null;
private final WindowStagger windowStagger;
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;
}
复制代码
TumblingEventTimeWindows
继承了WindowAssigner
和TimeWindow
,关键的参数是size和globalOffset
通过以下两个方法计算维持了一个时间窗口。flink的滚动时间处理窗口,代码简练而又有效。
// Long.MIN_VALUE is currently assigned when no timestamp is present
long start =
TimeWindow.getWindowStartWithOffset(
timestamp, (globalOffset + staggerOffset) % size, size);
public static long getWindowStartWithOffset(long timestamp, long offset, long windowSize) {
final long remainder = (timestamp - offset) % windowSize;
// handle both positive and negative cases
if (remainder < 0) {
return timestamp - (remainder + windowSize);
} else {
return timestamp - remainder;
}
}
复制代码
还有就是TumblingProcessingTimeWindows
,在这两个timeWindow的代码层面几乎上完全一摸一样,都是通过getWindowStartWithOffset
方法来计算时间,那么他们的不同之处,也就是一个用机器时间,一个用元素时间来计算窗口,是在哪个部分来体现的呢。TumblingProcessingTimeWindows
的获取当前时间是通过整个环境参数获取
final long now = context.getCurrentProcessingTime();
复制代码
而TumblingEventTimeWindows
的时间是通过assignWindows
的参数 long timestamp
传入的。
其实是在使用的时候,也就是比如下文的代码。timewindow底层用的就是用的就是默认的滚动窗口,但是使用的究竟是基于处理时间的滚动窗口,还是基于事件时间的滚动窗口,是由StreamExecutionEnvironment
环境参数决定的。 在一开始创建StreamExecutionEnvironment
环境的时候,就可以设置数据流的TimeCharacteristic
,也就决定了timeWindow底层会使用哪种处理方式
/** The time characteristic that is used if none other is set. */
private static final TimeCharacteristic DEFAULT_TIME_CHARACTERISTIC =
TimeCharacteristic.EventTime;
复制代码
@Deprecated
public AllWindowedStream<T, TimeWindow> timeWindowAll(Time size) {
if (environment.getStreamTimeCharacteristic() == TimeCharacteristic.ProcessingTime) {
return windowAll(TumblingProcessingTimeWindows.of(size));
} else {
return windowAll(TumblingEventTimeWindows.of(size));
}
}