Flink DataStream 窗口介绍及使用由于工作需要最近学习flink
现记录下Flink介绍和实际使用过程
这是flink系列的第四篇文章
窗口介绍时间窗口
翻滚窗口(数据以一个时间断为节点不会有重复)滑动窗口会话窗口全局窗口 窗口函数
减少函数聚合函数进程窗口函数
窗口介绍Flink 认为 Batch 是 Streaming 的一个特例,所以 Flink 底层引擎是一个流式引擎,在上面实现了流处理和批处理。而窗口(window)就是从 Streaming 到 Batch 的一个桥梁。Flink 提供了非常完善的窗口机制。
官方中文文档地址:
https://nightlies.apache.org/flink/flink-docs-release-1.14/zh/docs/dev/datastream/operators/windows/
因为本人目前接触的都是每来一个数据就处理一次的流式数据,所以对窗口的概念和使用场景一直比较模糊,下方的介绍可以说是点醒了我。
在流处理应用中,数据是连续不断的,因此我们不可能等到所有数据都到了才开始处理。当然我们可以每来一个消息就处理一次,但是有时我们需要做一些聚合类的处理,例如:在过去的1分钟内有多少用户点击了我们的网页。在这种情况下,我们必须定义一个窗口,用来收集最近一分钟内的数据,并对这个窗口内的数据进行计算。
如果在数据流上,截取固定大小的一部分,这部分是可以进行统计的。 截取方式主要有两种:
- 根据时间进行截取(time-driven-window),比如每1分钟统计一次或每10分钟统计一次。根据消息数量进行截取(data-driven-window),比如每5个数据统计一次或每50个数据统计一次。
按照时间来进行窗口划分,每次窗口的滑动距离等于窗口的长度,这样数据不会重复计算,我们参考上面的案例
翻滚窗口java使用示例
DataStreaminput = ...; // tumbling event-time windows input .keyBy( ) .window(TumblingEventTimeWindows.of(Time.seconds(5))) . ( ); // tumbling processing-time windows input .keyBy( ) .window(TumblingProcessingTimeWindows.of(Time.seconds(5))) . ( ); // daily tumbling event-time windows offset by -8 hours. input .keyBy( ) .window(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8))) . ( );
可以使用Time.milliseconds(x)、Time.seconds(x)、 Time.minutes(x)等之一指定时间间隔。
滑动窗口如上一个示例所示,翻转窗口分配器还采用一个可选offset 参数,可用于更改窗口的对齐方式。例如,在没有偏移量的情况下,每小时翻滚的窗口与
epoch 对齐,也就是说,您将获得诸如 等的 1:00:00.000 - 1:59:59.999窗口2:00:00.000 -
2:59:59.999。如果你想改变它,你可以给一个偏移量。例如,使用 15 分钟的偏移量,您可以获取 1:15:00.000 -
2:14:59.999等2:15:00.000 - 3:14:59.999。偏移量的一个重要用例是将窗口调整为 UTC-0
以外的时区。例如,在中国,您必须指定偏移量Time.hours(-8).
数据在某一个时间段内会有重叠,也就是说数据会重复
按照时间来进行窗口划分,每次窗口的滑动距离小于窗口的长度,这样数据就会有一部分重复计算。
滑动窗口java使用示例
DataStreaminput = ...; // sliding event-time windows input .keyBy( ) .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5))) . ( ); // sliding processing-time windows input .keyBy( ) .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5))) . ( ); // sliding processing-time windows offset by -8 hours input .keyBy( ) .window(SlidingProcessingTimeWindows.of(Time.hours(12), Time.hours(1), Time.hours(-8))) . ( );
可以使用Time.milliseconds(x)、Time.seconds(x)、 Time.minutes(x)等之一指定时间间隔。
如上一个示例所示,滑动窗口分配器还带有一个可选offset参数,可用于更改窗口的对齐方式。例如,如果没有偏移量,每小时滑动 30
分钟的窗口与 epoch 对齐,即您将获得诸如 等的 1:00:00.000 - 1:59:59.999窗口1:30:00.000 -
2:29:59.999。如果你想改变它,你可以给一个偏移量。例如,使用 15 分钟的偏移量,您可以获取 1:15:00.000 -
2:14:59.999等1:45:00.000 - 2:44:59.999。偏移量的一个重要用例是将窗口调整为 UTC-0
以外的时区。例如,在中国,您必须指定偏移量Time.hours(-8).
会话窗口如果窗口计算时间 > 窗口时间,会出现数据丢失
如果窗口计算时间 < 窗口时间,会出现数据重复计算
如果窗口计算时间 = 窗口时间,数据不会被重复计算
会话窗口分配器按活动会话对元素进行分组。与翻滚窗口和滑动窗口相比,会话窗口不重叠,也没有固定的开始和结束时间。相反,当会话窗口在一段时间内没有接收到元素时,即当出现不活动间隙时,会话窗口将关闭。会话窗口分配器可以配置有静态会话间隙或 会话间隙提取器功能,该功能定义不活动的时间长度。当此期限到期时,当前会话关闭,后续元素被分配到新的会话窗口。
DataStream全局窗口input = ...; // event-time session windows with static gap input .keyBy( ) .window(EventTimeSessionWindows.withGap(Time.minutes(10))) . ( ); // event-time session windows with dynamic gap input .keyBy( ) .window(EventTimeSessionWindows.withDynamicGap((element) -> { // determine and return session gap })) . ( ); // processing-time session windows with static gap input .keyBy( ) .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10))) . ( ); // processing-time session windows with dynamic gap input .keyBy( ) .window(ProcessingTimeSessionWindows.withDynamicGap((element) -> { // determine and return session gap })) . ( );
全局窗口分配器将具有相同键的所有元素分配给同一个全局窗口。此窗口方案仅在您还指定自定义触发器时才有用。否则,将不会执行任何计算,因为全局窗口没有自然结束,我们可以在该端处理聚合元素。
DataStream窗口函数input = ...; input .keyBy( ) .window(GlobalWindows.create()) . ( );
在定义了窗口分配器之后,我们需要指定我们想要在每个窗口上执行的计算。这是窗口函数的职责,一旦系统确定一个窗口已准备好处理,该函数用于处理每个(可能是键控的)窗口的元素(请参阅触发器以了解 Flink 如何确定窗口何时准备好)。
窗口函数可以是ReduceFunction、AggregateFunction或ProcessWindowFunction。前两个可以更有效地执行(参见状态大小部分),因为 Flink 可以在每个窗口到达时增量聚合元素。AProcessWindowFunction获取一个Iterable窗口中包含的所有元素的一个,以及有关元素所属窗口的附加元信息。
减少函数AReduceFunction指定如何组合输入中的两个元素以生成相同类型的输出元素。Flink 使用 aReduceFunction来增量聚合窗口的元素。
DataStream> input = ...; input .keyBy( ) .window( ) .reduce(new ReduceFunction >() { public Tuple2 reduce(Tuple2 v1, Tuple2 v2) { return new Tuple2<>(v1.f0, v1.f1 + v2.f1); } });
上面的示例相加了窗口中所有元素的元组的第二个字段。
聚合函数AnAggregateFunction是 a 的通用版本ReduceFunction,具有三种类型:输入类型 ( IN)、累加器类型 ( ACC) 和输出类型 ( OUT)。输入类型是输入流中元素的类型,并且AggregateFunction具有将一个输入元素添加到累加器的方法。该接口还具有用于创建初始累加器、将两个累加器合并为一个累加器以及OUT从累加器中提取输出(类型为 )的方法。我们将在下面的示例中看到它是如何工作的。
与ReduceFunction相同,Flink 将在窗口的输入元素到达时增量聚合它们。
private static class AverageAggregate
implements AggregateFunction, Tuple2, Double> {
@Override
public Tuple2 createAccumulator() {
return new Tuple2<>(0L, 0L);
}
@Override
public Tuple2 add(Tuple2 value, Tuple2 accumulator) {
return new Tuple2<>(accumulator.f0 + value.f1, accumulator.f1 + 1L);
}
@Override
public Double getResult(Tuple2 accumulator) {
return ((double) accumulator.f0) / accumulator.f1;
}
@Override
public Tuple2 merge(Tuple2 a, Tuple2 b) {
return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
}
}
DataStream> input = ...;
input
.keyBy()
.window()
.aggregate(new AverageAggregate());
上面的示例计算窗口中元素的第二个字段的平均值。
进程窗口函数ProcessWindowFunction 获得一个包含窗口所有元素的 Iterable,以及一个可以访问时间和状态信息的 Context 对象,这使得它能够提供比其他窗口函数更大的灵活性。这是以性能和资源消耗为代价的,因为元素不能增量聚合,而是需要在内部缓冲,直到窗口被认为准备好处理。
public abstract class ProcessWindowFunctionimplements Function { public abstract void process( KEY key, Context context, Iterable elements, Collector out) throws Exception; public abstract class Context implements java.io.Serializable { public abstract W window(); public abstract long currentProcessingTime(); public abstract long currentWatermark(); public abstract KeyedStateStore windowState(); public abstract KeyedStateStore globalState(); } }



