5、Flink中的Window

一、Window

1.Window概述

       streaming流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而window是一种切割无限数据为有限块进行处理的手段。
       Window是无限数据流处理的核心,Window将一个无限的stream拆分成有限大小的"buckets"桶,我们可以在这些桶上做计算操作。

2.Window类型

Window可以分为两类:
1)CountWindow:按照指定数据条数生成一个Window,与时间无关。
2)TimeWindow:按照时间生成Window。Time Window可以根据窗口的实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。

2.1滚动窗口(Tumbling Windows)

将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。例如:如果你指定了一个5分钟大小的滚动窗口,窗口的创建如下图所示:
在这里插入图片描述
出现在边界的数据要根据规定划分,例如时间前闭后开、前开后闭等等。
适用场景:适合做BI统计等(做每个时间段的聚合计算)

2.2滑动窗口(Sliding Windows)

滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定,可以有重叠。
滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。
例如,你有10分钟的窗口和5分钟的滑动,那么每个窗口中5分钟的窗口里包含着上个10分钟产生的数据,如下图所示:
在这里插入图片描述
实际上滚动窗口时滑动窗口的一个特例,它的窗口长度和滑动间隔相等。
适用场景:对最近一个时间段内的统计(求某接口5最近5min的失败率来决定是否要报警)

2.3会话窗口(Session Windows)

由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。
在这里插入图片描述

二、Window API

Flink里的window函数一般在keyBy后面使用,如果不使用keyBy,那么只有windowAll等类似的函数,windowAll是把所有的数据放到一个分区内,并行度为1。
在这里插入图片描述
窗口函数一般是和聚合函数一块使用的。

1.TimeWindow

TimeWindow 是将指定时间范围内的所有数据组成一个 window,一次对一个window 里面的所有数据进行计算。
1)滚动窗口
Flink 默认的时间窗口根据 Processing Time 进行窗口的划分,将 Flink 获取到的数据根据进入 Flink 的时间划分到不同的窗口中。
i.使用Window函数:
使用Winow函数要求我们传一个WindowAssigner类型的数据,而这个类的构造方法是protected类型的,所以不能new,得使用它的别的方法来获取一个它的对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码如下:

    mapResult.keyBy(data->data.getId()).window(TumblingEventTimeWindows.of(Time.seconds(15)).minBy(1);

这里直接声明了一个滚动时间窗口TumblingEventTimeWindows
ii.使用TimeWindow函数:
代码如下:

    mapResult.keyBy(data->data.getId()).timeWindow(Time.seconds(15)).minBy(1);

简单粗暴,直接使用timeWindow声明时间窗口,这里timeWindow有两个方法,一个参数的方法代表的是滚动时间窗口,两个参数的方法代表的是滑动时间的窗口。
在这里插入图片描述
2)滑动窗口
滑动窗口跟滚动窗口类似,只是调用的方法是两个参数的方法,代码如下:

    mapResult.keyBy(data->data.getId()).timeWindow(Time.seconds(15),Time.seconds(5)).minBy(1);

3)会话窗口
会话窗口没有那么简单的timeWindow方法,只能通过Window方法来调用,代码如下:

    mapResult.keyBy(data->data.getId())
             .window(ProcessingTimeSessionWindows.withGap(Time.seconds(15)))
             .minBy(1);

2.CountWindow

CountWindow 根据窗口中相同 key 元素的数量来触发执行,执行时只计算元素数量达到窗口大小的 key 对应的结果,与时间无关。
注意:CountWindow 的 window_size 指的是相同 Key 的元素的个数,不是输入的所有元素的总数。
计数窗口也分为滚动计数窗口和滑动计数窗口,它们的创建方式和时间窗口里的滚动、滑动窗口一样,代码如下:

    //滚动计数窗口
    mapResult.keyBy(data->data.getId())
             .countWindow(10)
             .minBy(1);
    
    //滑动计数窗口
    mapResult.keyBy(data->data.getId())
            .countWindow(10,5)
            .minBy(1);

3.Window Fcuntion

Window function定义了要对窗口中收集的数据做的计算操作,主要分为两类。

3.1 增量聚合函数

增量聚合函数(incremental aggregation functions)每条数据来就进行计算,保持一个简单的状态。典型的增量聚合函数有ReduceFunction、AggregateFunction。
1)ReduceFunction
跟之前reduce时定义的ReduceFunction一样。

    SingleOutputStreamOperator<SensorReading> result = mapResult.keyBy(data -> data.getId())
            .timeWindow(Time.seconds(3))
            .reduce(new ReduceFunction<SensorReading>() {
                @Override
                public SensorReading reduce(SensorReading oldData, SensorReading newData) throws Exception {
                    return new SensorReading(oldData.getId(), newData.getTime(), Math.max(oldData.getTemperature(), newData.getTemperature()));
                }
            });

2)AggregateFunction

    SingleOutputStreamOperator<Integer> result = mapResult.keyBy(data -> data.getId())
            .timeWindow(Time.seconds(10))
            .aggregate(new AggregateFunction<SensorReading, Integer, Integer>() {

                @Override
                public Integer createAccumulator() {
                    return 0;
                }

                @Override
                public Integer add(SensorReading sensorReading, Integer integer) {
                    return integer + 1;
                }

                @Override
                public Integer getResult(Integer integer) {
                    return integer;
                }

                @Override
                public Integer merge(Integer integer, Integer acc1) {
                    return integer + acc1;
                }
            });

    result.print();

new AggregateFunction<SensorReading, Integer, Integer>()中第一个参数是要处理的数据的类型,第二个参数是累加器计算的结果的类型,第三个参数是返回值的类型
createAccumulator()是对累加器的值进行初始化
add(SensorReading sensorReading, Integer integer)是进行累加器的计算,integer是当前累加器的值
getResult(Integer integer)是返回累加器的最终结果值
merge(Integer integer, Integer acc1)是对两个分区的值进行合并

3.2全窗口聚合函数

全窗口聚合函数是收集一段时间内所有的数据,然后进行统计,最后输出,它的方法有apply等等
apply函数使用方法如下:

    DataStream<Tuple3<String, Integer, Long>> result = mapResult.keyBy("id")
            .timeWindow(Time.seconds(15))
            .apply(new WindowFunction<SensorReading, Tuple3<String, Integer, Long>, Tuple, TimeWindow>() {
                @Override
                public void apply(Tuple tuple, TimeWindow timeWindow, Iterable<SensorReading> iterable, Collector<Tuple3<String, Integer, Long>> collector) throws Exception {
                    Integer count = IteratorUtils.toList(iterable.iterator()).size();
                    String id = tuple.getField(0);
                    Long windowEnd = timeWindow.getEnd();
                    collector.collect(new Tuple3<String, Integer, Long>(id, count, windowEnd));
                }
            });
    result.print();

代码说明:
apply方法要求我们实现WindowFunction<SensorReading, Tuple3<String, Integer, Long>, Tuple, TimeWindow>接口,它的第一个参数是处理的数据类型,第二个参数是返回值的类型,第三个参数是前面keyBy分区后的key的类型,第四个参数是时间窗口。
实现接口要求我们在内部实现apply方法,apply(Tuple tuple, TimeWindow timeWindow, Iterable iterable, Collector<Tuple3<String, Integer, Long>> collector),它的第一个参数是前面提到的key的类型,第二个参数是时间窗口,可以获得相关的一些信息,第三个参数是收集到的所有数据的迭代器,第四个参数是用来输出的。

3.3 计数窗口实例

上面两种都采用的时间窗口,这里采用计数窗口进行简单的测试。
需求:统计10个传感器的温度的平均值
分析:reduce方法输出类型不能改变,因此不适合统计平均值之类;而AggregateFunction方法可以自定义返回值和累加器的类型,因此采用AggregateFunction。
代码如下:

    DataStream<Double> result = mapResult.keyBy(data -> data.getId())
            .countWindow(10, 2)
            .aggregate(new AggregateFunction<SensorReading, Tuple2<Double, Integer>, Double>() {
                @Override
                public Tuple2<Double, Integer> createAccumulator() {
                    return new Tuple2<>(0.0, 0);
                }

                @Override
                public Tuple2<Double, Integer> add(SensorReading sensorReading, Tuple2<Double, Integer> doubleIntegerTuple2) {
                    return new Tuple2<>(doubleIntegerTuple2.f0 + sensorReading.getTemperature(), doubleIntegerTuple2.f1 + 1);
                }

                @Override
                public Double getResult(Tuple2<Double, Integer> doubleIntegerTuple2) {
                    return doubleIntegerTuple2.f0 / doubleIntegerTuple2.f1;
                }

                @Override
                public Tuple2<Double, Integer> merge(Tuple2<Double, Integer> doubleIntegerTuple2, Tuple2<Double, Integer> acc1) {
                    return new Tuple2<>(doubleIntegerTuple2.f0 + acc1.f0, doubleIntegerTuple2.f1 + acc1.f1);
                }
            });
    result.print();

代码说明:
AggregateFunction<SensorReading, Tuple2<Double, Integer>, Double>三个参数,第一个参数是要处理的数据类型,第二个参数是累加器的类型,第三个参数是返回值的类型。
执行结果:
在这里插入图片描述
执行之后会发现,输入两个值的时候会输出前两个值的平均温度,输入四个值的时候会输出前四个值的平均温度,所以当开始输入数据时,如果数据达不到窗口的大小,会自动往前面补0。然后根据滑动步长去输出,比如输入两个数时,前面补8个0,输入4个数的时候,前面步6个0…

4.其他可选API

1).trigger()
触发器。
定义window什么时候关闭,触发计算并输出结果
假如要处理迟到数据,8-9点的数据,到9点是否要关闭窗口,是否要计算并输出结果,实际上可以先不关闭窗口,先计算出来一个结果,让窗口停留一段时间,在时间内若数据发生改变再更新数据,所以关闭窗口和计算结果是分开的
2).evitor()
移除器。定义移除某些数据的逻辑,有点过滤数据的味道
3).allowedLateness()
允许处理迟到的数据。
到9点时先计算输出结果,先不关闭窗口,有数据来了再更新数据,实现了lambada架构的效果,到允许迟到的时间之后真正关闭窗口
4).sideOutputLateData()
将迟到的数据放入侧输出流。
将真正关闭窗口后的迟到的数据放到侧输出流中。

5).getSideOutput()
获取侧输出流里的结果。只能在计算完成之后,在外部获取测输出流。
代码如下:

        OutputTag<SensorReading> outputTag=new OutputTag<SensorReading>("late"){};
        SingleOutputStreamOperator<SensorReading> sum = mapResult.keyBy("id")
                .timeWindow(Time.seconds(15))
				//.trigger()
                //.evictor()
                .allowedLateness(Time.minutes(1))
                .sideOutputLateData(outputTag)
                .sum("temperature");
        sum.getSideOutput(outputTag).print("late");

总结:
window和聚合函数是必需的,其他都是可选的。
在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值