04-Flink Window API

1. Window 简介

概念

  • 一般真实的流都是无界的,怎么处理无界的数据?
  • 可以把无限的数据流进行切分,得到有限的数据集进行处理(即转换为有界流)。
  • 窗口(window)就是将无限流切割为有限流的一种方式,它会将流数据分发到有限大小的桶(bucket)进行分析。

在这里插入图片描述

窗口类型

  • 时间窗口(Time Window)

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

    • 滚动计数窗口
    • 滑动计数窗口

滚动窗口(Tumbling Windows)

在这里插入图片描述

  • 将数据以固定的窗口长度对数据进行切分。
  • 时间对齐,窗口长度固定,没有重叠。

滑动窗口(Sliding Windows)

在这里插入图片描述

  • 滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
  • 窗口长度固定,可以有重叠。

会话窗口(Session Windows)

在这里插入图片描述

  • 由一系列事件组合一个指定时间长度的 timeout 间隔组成,也就是一段时间没有接收到新数据就会生成新的窗口。
  • 特点:时间无对齐。

2. Window API

        window():

  • 我们可以用.window()来定义一个窗口,然后基于这个window去做一些聚合或者其他处理操作。注意window()方法必须在keyBy之后才能用。
  • Flink提供了更加简单的.timeWindow.countWindow方法,用于定于时间窗口和计数窗口。
public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

//        // 从文件中读取数据
        DataStream<String> inputStream = env.readTextFile("test.txt");

        // 转换成 Score 类型
        DataStream<Score> dataStream = inputStream.map(new MapFunction<String, Score>() {
            @Override
            public Score map(String value) throws Exception {
                String[] array = value.split(",");
                return new Score(array[0], array[1], Integer.parseInt(array[2]));
            }
        });

        // 开窗测试
        dataStream.keyBy("course")
//                .countWindow(10, 2);
//                .window(EventTimeSessionWindows.withGap(Time.minutes(1)));
//                .timeWindow(Time.seconds(2));
                  .window(TumblingEventTimeWindows.of(Time.seconds(15)));

        dataStream.print();

        env.execute();

WindowAssigne

  • window()方法接收的输入参数是一个 WindowAssigne
  • WindowAssigne 负责将每条输入到数据分发到正确到window
  • Flink提供了通用的WindowAssigne
    • 滚动窗口(tumbling window
    • 滑动窗口(sliding window
    • 会话窗口(session window
    • 全局窗口 (global window

窗口创建

  • 滚动时间窗口
	.timeWindow(Time.seconds(15))
  • 滑动时间窗口
	.timeWindow(Time.seconds(15), Time.seconds(5))
  • 会话窗口
	.window(EventTimeSessionWindows.withGap(Time.minutes(5)))
  • 滚动计数窗口
	.countWindow(5)
  • 滑动计数窗口
	.countWindow(5, 2)

窗口函数

  • window function定义了要对窗口中收集的数据做的计算操作
  • 可以分为两类:
    • 增量聚合函数
      • 每条数据到来就进行计算,保持一个简单的状态
      • Reduce Function, AggregateFunction
    • 全窗口函数
      • 先把窗口所有数据收集起来,等到计算的时候会遍历所有数据
      • ProcessWindowFunction, WIndowFunction

增量聚合函数

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);


        // socket 文本流
        DataStream<String> inputStream = env.socketTextStream("localhost", 7777);

        // 转换成 Score 类型
        DataStream<Score> dataStream = inputStream.map(new MapFunction<String, Score>() {
            @Override
            public Score map(String value) throws Exception {
                String[] array = value.split(",");
                return new Score(array[0], array[1], Integer.parseInt(array[2]));
            }
        });

        // 增量窗口
        DataStream<Integer> resultStream = dataStream.keyBy("course")
                .timeWindow(Time.seconds(15))
                .aggregate(new AggregateFunction<Score, Integer, Integer>() {
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    @Override
                    public Integer add(Score score, Integer count) {
                        System.out.println("add");
                        return count + 1;
                    }

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

                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                });
        resultStream.print("resultStream");
        env.execute();
    }

在这里插入图片描述
       上述代码的作用,就是开启一个滚动时间窗口,时间间隔为 15 秒,每隔 15 秒,对数据做一次增量聚合操作,统计各科目出现的次数。

在这里插入图片描述
       我们每 15 秒,输入红框内的数据,可以猜测一下结果。

       每输入一条数据,都会进行一次 add,当到达15秒后,将聚合结果输出。

在这里插入图片描述

全量窗口函数

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);


        // socket 文本流
        DataStream<String> inputStream = env.socketTextStream("localhost", 7777);

        // 转换成 Score 类型
        DataStream<Score> dataStream = inputStream.map(new MapFunction<String, Score>() {
            @Override
            public Score map(String value) throws Exception {
                String[] array = value.split(",");
                return new Score(array[0], array[1], Integer.parseInt(array[2]));
            }
        });

        // 全窗口函数
        DataStream<Tuple3<String, Long, Integer>> resultStream = dataStream.keyBy("course")
                .timeWindow(Time.seconds(15))
                .apply(new WindowFunction<Score, Tuple3<String, Long, Integer>, Tuple, TimeWindow>() {
                    @Override
                    public void apply(Tuple tuple, TimeWindow window, Iterable<Score> input, Collector<Tuple3<String, Long, Integer>> out) throws Exception {
                        String course = tuple.getField(0);
                        Long windowEnd = window.getEnd();
                        Integer count = IteratorUtils.toList(input.iterator()).size();
                        out.collect(new Tuple3<String, Long, Integer>(course, windowEnd, count));
                    }
                });
        resultStream.print("resultStream2");

        env.execute();
    }

       我们同样按照上面的方式进行数据的输出,观察输出。

在这里插入图片描述
       可以看到,全量实在 15 秒到达时,统一做了聚合计算。

在这里插入图片描述

计数窗口

       创建一个滚动计数窗口,当各科目个数达到 3 时,则计算平均值。

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        
        // socket 文本流
        DataStream<String> inputStream = env.socketTextStream("localhost", 7777);

        // 转换成Score类型
        DataStream<Score> dataStream = inputStream.map(new MapFunction<String, Score>() {
            public Score map(String value) throws Exception {
                String[] array = value.split(",");
                return new Score(array[0], array[1], Integer.parseInt(array[2]));
            }
        });

        // 开窗测试
        // 计数窗口
        DataStream<Double> resStream = dataStream.keyBy("course")
                .countWindow(3)
                .aggregate(new AggregateFunction<Score, Tuple2<Double, Integer>, Double>() {

                    @Override
                    public Tuple2<Double, Integer> createAccumulator() {
                        return new Tuple2<>(0.0, 0);
                    }

                    @Override
                    public Tuple2<Double, Integer> add(Score score, Tuple2<Double, Integer> accumulator) {
                        return new Tuple2<>(accumulator.f0 + score.getScore(), accumulator.f1 + 1);
                    }

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

                    @Override
                    public Tuple2<Double, Integer> merge(Tuple2<Double, Integer> a, Tuple2<Double, Integer> b) {
                        return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
                    }
                });

        resStream.print();
        
        env.execute();
    }

       我们首先输入如下三个成绩,可以看到输出为 1.5 (1 + 2 + 3)/3 = 1.5,这是因为我们计数窗口的 size 为 3,所以在接受到 3 个数字后,便会进行输出。

在这里插入图片描述
在这里插入图片描述
       接着再输出一个成绩,此时并没有输出,是因为语文重新开始计数,个数不足 3 个。

在这里插入图片描述

在这里插入图片描述

       接着再输出三个数学成绩,可以看到已经计算出了对应的平均值。(5 + 6 +7) = 6.0
在这里插入图片描述

在这里插入图片描述

       上面就是滚动计数窗口的用法,还是很容易理解的。

       接下来的话,我们创建一个滑动计数窗口,每两个元素,计算一次最近 3 个元素的值。

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // socket 文本流
        DataStream<String> inputStream = env.socketTextStream("localhost", 7777);

        // 转换成Score类型
        DataStream<Score> dataStream = inputStream.map(new MapFunction<String, Score>() {
            public Score map(String value) throws Exception {
                String[] array = value.split(",");
                return new Score(array[0], array[1], Integer.parseInt(array[2]));
            }
        });

        // 计数窗口
        DataStream<Double> resStream = dataStream.keyBy("course")
                .countWindow(3, 2)
                .aggregate(new AggregateFunction<Score, Tuple2<Double, Integer>, Double>() {

                    @Override
                    public Tuple2<Double, Integer> createAccumulator() {
                        return new Tuple2<>(0.0, 0);
                    }

                    @Override
                    public Tuple2<Double, Integer> add(Score score, Tuple2<Double, Integer> accumulator) {
                        return new Tuple2<>(accumulator.f0 + score.getScore(), accumulator.f1 + 1);
                    }

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

                    @Override
                    public Tuple2<Double, Integer> merge(Tuple2<Double, Integer> a, Tuple2<Double, Integer> b) {
                        return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
                    }
                });

        resStream.print();

        env.execute();
    }

       我们输入两个成绩,可以看到输出 1.5。这是因为每两个元素,计算最近 3 个元素的平均值,但是由于并没有第三个元素,所以输出(1 + 2) / 2 = 1.5
在这里插入图片描述

在这里插入图片描述
       此时,在输入一个元素,可以看到并没有输出,因为重新开始计数,个数并不足 2 个。
在这里插入图片描述

在这里插入图片描述

       接着在输入一个元素,次数输出 3 是因为(2 + 3 + 4) / 3 = 3
在这里插入图片描述

在这里插入图片描述

       通过上述两个案例,想必可以理解滚动与滑动的区别。

会话窗口

       会话窗口在 WaterMark 之后进行补充。

3. 其他可选 API

  • .trigger() ---- 触发器
  • .evictor() ---- 移除器
  • .allowedLateness() ---- 允许处理迟到的数据
  • .sideOutputLateData() ---- 将迟到的数据放入侧输出流
  • .getSideOutput() — 获取侧输出流
        OutputTag<Score> outputTag = new OutputTag<Score>("late") {};
        SingleOutputStreamOperator<Score> sumStream = dataStream.keyBy("course")
                .timeWindow(Time.seconds(15))
//                .trigger()
//                .evictor()
                .allowedLateness(Time.minutes(1))
                .sideOutputLateData(outputTag)
                .sum("score");

        sumStream.getSideOutput(outputTag).print("late");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值