Apache Flink学习笔记(9)Flink中流处理中Window

窗口概念

在大多数场景下,我们需要统计的数据流都是无界的,因此我们无法等待整个数据流终止后才进行统计。通常情况下,我们只需要对某个时间范围或者数量范围内的数据进行统计分析:如每隔五分钟统计一次过去一小时内所有商品的点击量;或者每发生1000次点击后,都去统计一下每个商品点击率的占比。在 Flink 中,我们使用窗口 (Window) 来实现这类功能。按照统计维度的不同,Flink 中的窗口可以分为 时间窗口 (Time Windows) 和 计数窗口 (Count Windows) 。

窗口(window)就是将无限流切割为有限流的一种方式,它会将流数据分发到有限大小的桶(bucket)中进行分析。

时间语义

在Flink中,如果以时间段划分边界的话,那么时间就是一个极其重要的字段。

Flink中的时间有三种类型,如下图所示:
在这里插入图片描述

  • Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink通过时间戳分配器访问事件时间戳。
  • Ingestion Time:数据进入Flink的时间,进入datasource的时间
  • Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是Processing Time。

我们往往更关心事件时间(Event Time)

Window

官方解释:流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而window是一种切割无限数据为有限块进行处理的手段。

所以Window是无限数据流处理的核心,Window将一个无限的stream拆分成有限大小的”buckets”桶,我们可以在这些桶上做计算操作。

并且开窗操作必须在keyby之后才能进行。

划分窗口就两种方式:

时间窗口(Time Window)

  • 滚动时间窗口
  • 滑动时间窗口
  • 会话窗口
    计数窗口(Count Window)
  • 滚动计数窗口
  • 滑动计数窗口

Time Windows

Time Windows 用于以时间为维度来进行数据聚合。

滚动窗口(Tumbling Window)

滚动窗口 (Tumbling Windows) 是指彼此之间没有重叠的窗口。例如:每隔1小时统计过去1小时内的商品点击量,那么 1 天就只能分为 24 个窗口,每个窗口彼此之间是不存在重叠的,具体如下:
在这里插入图片描述

图解:

  • user1,user2,user3代表不同的分组。
  • 滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。

TumblingTimeWindow API

假设滚动窗口的大小为15s,那么滑动步长也是15s,也就是每隔20s做一次计算并输出,然后滑动到下一个窗口。

输入数据:

sensor_1,1547718147,12
sensor_6,1547718201,15.0
sensor_6,1547718202,15.0
sensor_6,1547718203,6.7
sensor_8,1547718204,38.0
sensor_8,1547718201,38.0
sensor_8,1547718202,15.4
sensor_1,1547718203,6.7
sensor_6,1547718204,38.1
import org.apache.commons.collections.IteratorUtils;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

public class TransFormTimeWindow {
    public static void main(String[] args) throws Exception {
        // 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 从nc中读取数据
        DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost", 7777);

        // map操作
        SingleOutputStreamOperator<SensorReading2> dataStream = stringDataStreamSource.map((String value) -> {
            String[] fields = value.split(",");
            return new SensorReading2(fields[0], new Long(fields[1]), new Double(fields[2]));
        }).setParallelism(1);

        KeyedStream<SensorReading2, String> keyedStream = dataStream.
                keyBy((SensorReading2 value) -> {
            return value.getId();
        });

        keyedStream
        		// 效果就是一个滚动时间窗口,以Processing Time为基准,每一个分组到达15s就统计一次并输出结果
                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
        		.aggregate(new AggregateFunction<SensorReading2, Integer, Integer>() {
            /*
             * @param <IN> The type of the values that are aggregated (input values)
             * @param <ACC> The type of the accumulator (intermediate aggregate state).
             * @param <OUT> The type of the aggregated result
             */

            /*
            创建累加器,给定初始值
             */
            @Override
            public Integer createAccumulator() {
                return 0;
            }

            /*
            对数据进行聚合
             */
            @Override
            public Integer add(SensorReading2 value, Integer accumulator) {
                return accumulator + 1;
            }

            /*
            累加器作为输出结果
             */
            @Override
            public Integer getResult(Integer accumulator) {
                return accumulator;
            }

            /*
            一般不用于滚动和滑动窗口,而用于session窗口
             */
            @Override
            public Integer merge(Integer a, Integer b) {
                return null;
            }
        }).print();
        env.execute();
    }

    public static class MyFlatMapper implements FlatMapFunction<String, Tuple2<String, Integer>> {
        private static final long serialVersionUID = -5224012503623543819L;

        @Override
        public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception{
            // 分词
            String[] words = value.split(" ");
            for (String word : words){
                out.collect(new Tuple2<String,Integer>(word,1));
            }
        }
    }

}

滑动窗口(Sliding Windows)

滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。不过滚动窗口的滑动步长等于窗口长度。一般来说滑动步长应该小于窗口大小。

在这里插入图片描述

图解:

  • 比较重要的是window要理解滑动窗口的初始状态
  • 如果是时间滑动窗口,当过了滑动步长的时间的时候,就应该在窗口内做一次统计。比如,滑动窗口15s,滑动步长5s,也就是初始状态过了5s就开始统计,但是窗口没有到达15s怎么办?这时候初始状态就是当成15s的窗口。这时候又过了5s,也就是一共过了10s,又会做一次统计,但是是在10s内做的一次统计,同理当成15s。又过了5s,这个时候一个15s窗口才出现,从此之后就会过5s完整统计15s内的数据,前几次可能会有误差。
  • 同理,如果是计数滑动窗口,例如,滑动窗口10个数据,滑动步长为5个数据,这个时候,初始状态一旦数据到了5个数据就会做一次统计,但是窗口还没有到10个数据,也就把它当成10个数据,然后开始滑步,每过5个,就在10个数据中做一统计。

SlidingTimeWindow API

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.benchmark.SensorReading2;

public class TransFormSlidingTimeWindow {
    public static void main(String[] args)throws Exception {
        // 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 从nc中读取数据
        DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost", 7777);

        // map操作
        SingleOutputStreamOperator<SensorReading2> dataStream = stringDataStreamSource.map((String value) -> {
            String[] fields = value.split(",");
            return new SensorReading2(fields[0], new Long(fields[1]), new Double(fields[2]));
        }).setParallelism(2);

        KeyedStream<SensorReading2, String> keyedStream = dataStream.keyBy((SensorReading2 value) -> {
            return value.getId();
        });

        keyedStream
        		// 每隔15s做一次统计,初始状态就算没到30s也要记继续滑动
                .window(SlidingProcessingTimeWindows.of(Time.seconds(30),Time.seconds(15)))
                // 增量聚合函数,对数据个数进行统计
                .aggregate(new AggregateFunction<SensorReading2, Integer, Integer>() {
                    /*
                     * @param <IN> The type of the values that are aggregated (input values)
                     * @param <ACC> The type of the accumulator (intermediate aggregate state).
                     * @param <OUT> The type of the aggregated result
                     */

                    /*
                    创建累加器,给定初始值
                     */
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    /*
                    对数据进行聚合
                     */
                    @Override
                    public Integer add(SensorReading2 value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    /*
                    累加器作为输出结果
                     */
                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    /*
                    一般不用于滚动和滑动窗口,而用于session窗口
                     */
                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                }).print();
        env.execute();
    }
}

会话窗口

在这里插入图片描述
session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。

总的来说就是设定一个时间间隔,如果这个间隔内没有数据,就会开启另一个窗口

SessionWindow API

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.ProcessingTimeSessionWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.benchmark.SensorReading2;

public class TransFormTimeSessionWindow {
    public static void main(String[] args)throws Exception {
        // 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 从nc中读取数据
        DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost", 7777);

        // map操作
        SingleOutputStreamOperator<SensorReading2> dataStream = stringDataStreamSource.map((String value) -> {
            String[] fields = value.split(",");
            return new SensorReading2(fields[0], new Long(fields[1]), new Double(fields[2]));
        }).setParallelism(2);

        KeyedStream<SensorReading2, String> keyedStream = dataStream.keyBy((SensorReading2 value) -> {
            return value.getId();
        });

        keyedStream
        		// 过了15s还没有来数据,就计算一次
                .window(ProcessingTimeSessionWindows.withGap(Time.seconds(15)))
                // 增量聚合函数,对数据个数进行统计
                .aggregate(new AggregateFunction<SensorReading2, Integer, Integer>() {
                    /*
                     * @param <IN> The type of the values that are aggregated (input values)
                     * @param <ACC> The type of the accumulator (intermediate aggregate state).
                     * @param <OUT> The type of the aggregated result
                     */

                    /*
                    创建累加器,给定初始值
                     */
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    /*
                    对数据进行聚合
                     */
                    @Override
                    public Integer add(SensorReading2 value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    /*
                    累加器作为输出结果
                     */
                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    /*
                    一般不用于滚动和滑动窗口,而用于session窗口
                     */
                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                }).print();
        env.execute();
    }
}

CountWindow

CountWindow根据窗口中相同key元素的数量来触发执行,执行时只计算元素数量达到窗口大小的key对应的结果。

滚动窗口(Tumbling Window)

CountWindow的window_size指的是相同Key的元素的个数,不是输入的所有元素的总数。例如,滚动窗口大小为10个,一个key为1的数据流到达10个就做一次统计,一个key为2的数据流到达了5个就不做你统计。

Tumbling CountWindow API

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.benchmark.SensorReading2;

public class TransFormTumblingCountWindow {
    public static void main(String[] args) throws Exception{
        // 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 从nc中读取数据
        DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost", 7777);

        // map操作
        SingleOutputStreamOperator<SensorReading2> dataStream = stringDataStreamSource.map((String value) -> {
            String[] fields = value.split(",");
            return new SensorReading2(fields[0], new Long(fields[1]), new Double(fields[2]));
        }).setParallelism(2);

        KeyedStream<SensorReading2, String> keyedStream = dataStream.keyBy((SensorReading2 value) -> {
            return value.getId();
        });

        keyedStream
        		// 每5个做一次统计数据数量,所以输出结果一直为5
                .countWindow(5)
                // 增量聚合函数,对数据个数进行统计
                .aggregate(new AggregateFunction<SensorReading2, Integer, Integer>() {
                    /*
                     * @param <IN> The type of the values that are aggregated (input values)
                     * @param <ACC> The type of the accumulator (intermediate aggregate state).
                     * @param <OUT> The type of the aggregated result
                     */

                    /*
                    创建累加器,给定初始值
                     */
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    /*
                    对数据进行聚合
                     */
                    @Override
                    public Integer add(SensorReading2 value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    /*
                    累加器作为输出结果
                     */
                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    /*
                    一般不用于滚动和滑动窗口,而用于session窗口
                     */
                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                }).print();
        env.execute();
    }
}

在这里插入图片描述

滑动窗口(Sliding Windows)

同样也是窗口长度和滑动窗口的操作:窗口长度是10,滑动长度是5,每隔5个做一次统计,但是初始状态到了5个数据的滑动长度却没有到达10个数据的窗口长度,所以初始状态窗口大小可以看做是10个数据的窗口,其余的位置为空。

如下图,窗口1中只有五个数据那么就对这五个数据做一次统计,滑动之后,才能达到10个数据大小,这个时候才是对10个数据进行统计。
在这里插入图片描述

Sliding CountWindow API

这里就做了一个计算数据个数的功能。

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.benchmark.SensorReading2;

public class TransFormSlidingCountWindow {
    public static void main(String[] args)throws Exception {
        // 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 从nc中读取数据
        DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost", 7777);

        // map操作
        SingleOutputStreamOperator<SensorReading2> dataStream = stringDataStreamSource.map((String value) -> {
            String[] fields = value.split(",");
            return new SensorReading2(fields[0], new Long(fields[1]), new Double(fields[2]));
        }).setParallelism(2);

        KeyedStream<SensorReading2, String> keyedStream = dataStream.keyBy((SensorReading2 value) -> {
            return value.getId();
        });

        keyedStream
                // 初始状态就是5个,对这5个数据进行计算输出,之后每个窗口就是10个,对诗歌数据进行计算
                .countWindow(10,5)
                // 增量聚合函数,对数据个数进行统计
                .aggregate(new AggregateFunction<SensorReading2, Integer, Integer>() {
                    /*
                     * @param <IN> The type of the values that are aggregated (input values)
                     * @param <ACC> The type of the accumulator (intermediate aggregate state).
                     * @param <OUT> The type of the aggregated result
                     */

                    /*
                    创建累加器,给定初始值
                     */
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    /*
                    对数据进行聚合
                     */
                    @Override
                    public Integer add(SensorReading2 value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    /*
                    累加器作为输出结果
                     */
                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    /*
                    一般不用于滚动和滑动窗口,而用于session窗口
                     */
                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                }).print();
        env.execute();
    }
}

在这里插入图片描述

窗口函数(window function)

在窗口处理之后,还需要聚合操作,如下所示:

可以分为两类

增量聚合函数(incremental aggregation functions)

  • 每条数据到来就进行计算,保持一个简单的状态
  • ReduceFunction, AggregateFunction

除此之外还可以直接使用max,maxby,sum等

全窗口函数(full window functions)

主要是使用apply函数

  • 先把窗口所有数据收集起来,等到计算的时候会遍历所有数据
  • ProcessWindowFunction,WindowFunction

ProcessWindowFunction和WindowFunction基本功能一直,不过ProcessWindowFunction可以获取的信息更多。

其它可选 API

  • .trigger() —— 触发器:定义 window 什么时候关闭,触发计算并输出结果
  • .evictor() —— 移除器:定义移除某些数据的逻辑
  • .allowedLateness() —— 允许处理迟到的数据
  • sideOutputLateData() —— 将迟到的数据放入侧输出流,这个和上面的允许处理迟到的数据配合使用,但是只能用于时间时间语义
  • getSideOutput() —— 获取侧输出流

window API 总览

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值