大数据开发Flink高级进阶之Window和Time的使用(第四十八篇)

45 阅读3分钟

一、Window窗口

Flink批处理是流处理的一个特例,所以Flink底层引擎是一个流式引擎,在上面实现了流处理和批处理,而window是流处理到批处理的一个桥梁

  1. Window是一种可以把无界数据切割为有界数据块的手段

    因为对流的所有元素进行计数是不可能的,因为通常流是无限的,或者称为是无界的。所以流上的聚合需要由window来划分范围

  2. Window可以是时间驱动的【Time Window】(eg:每30秒)或者数据驱动的【Count Window】(eg:每100个元素)

    DataStream API提供了基于Time和Count的Window。DataStream API也提供了定制化的Window操作

1.1、TimeWindow
  1. tumbling window 滚动窗口(没有重叠)表示窗口数据没有重叠
  2. sliding window 滑动窗口(有重叠)

timeWindow(Time.seconds(10)) 表示滚动窗口的窗口大小为10秒,对每10秒内的数据进行聚合计算

timeWindow(Time.seconds(10),Time.seconds(5)) 表示滑动窗口的窗口大小为10秒,滑动间隔为5秒,就是每隔5秒计算前10秒内的数据

package com.strivelearn.flink.window;
​
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
​
/**
 * @author strivelearn
 * @version TimeWindowOp.java, 2023年01月17日
 */
public class TimeWindowOp {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataSourceWithSocket = executionEnvironment.socketTextStream("192.168.234.100", 9001);
        // 需要设置时间语义
        executionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
        // TimeWindow 之 滚动窗口 ,每隔10s计算一次前10s时间窗口内的数据
        // dataSourceWithSocket.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
        //     @Override
        //     public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
        //         String[] words = s.split(" ");
        //         for (String word : words) {
        //             collector.collect(new Tuple2<String, Integer>(word, 1));
        //         }
        //     }
        // }).keyBy(0).timeWindow(Time.seconds(10)).sum(1).print();
​
        // TimeWindow 之 滑动窗口 ,每隔5s计算一次前10s时间窗口内的数据
        dataSourceWithSocket.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] words = s.split(" ");
                for (String word : words) {
                    collector.collect(new Tuple2<String, Integer>(word, 1));
                }
            }
        }).keyBy(0).
        // 第一个参数:窗口大小,第二个参数:滑动间隔
                            timeWindow(Time.seconds(10), Time.seconds(5)).sum(1).print();
        executionEnvironment.execute("TimeWindowOp");
    }
}

image-20230117214721238

image-20230117214735060

1.2、CountWindow

根据元素个数对数据流切分窗口,CountWindow也可以支持滚动窗口和滑动窗口

countWindow(5) 表示滚动窗口的大小是5个元素,也就是当窗口中填满5个元素的时候,就会对窗口进行计算了

countWindow(5,1) 表示滑动窗口的窗口大小是5个元素,滑动的间隔为1个元素,也就是说每新增1个元素就会对前面5个元素计算一次

package com.strivelearn.flink.window;
​
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
​
/**
 * @author strivelearn
 * @version CountWindowOp.java, 2023年01月17日
 */
public class CountWindowOp {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataSourceWithSocket = executionEnvironment.socketTextStream("192.168.234.100", 9001);
        // 需要设置时间语义
        executionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
        // CountWindow 之 滚动窗口 ,每隔5个元素计算一次前5个元素
        // dataSourceWithSocket.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
        //     @Override
        //     public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
        //         String[] words = s.split(" ");
        //         for (String word : words) {
        //                         collector.collect(new Tuple2<String, Integer>(word, 1));
        //         }
        //     }
        // }).keyBy(0).countWindow(5).sum(1).print();
​
        // TimeWindow 之 滑动窗口 ,每隔5s计算一次前10s时间窗口内的数据
        dataSourceWithSocket.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] words = s.split(" ");
                for (String word : words) {
                    collector.collect(new Tuple2<String, Integer>(word, 1));
                }
            }
        }).keyBy(0).
                            // 第一个参数:窗口大小,第二个参数:滑动间隔
                            countWindow(5,1).sum(1).print();
        executionEnvironment.execute("TimeWindowOp");
    }
}

image-20230117220539299

image-20230117220518992

1.3、自定义window

其实window还可以再细分一下,一种是基于key的window,一种是不基于key的window

package com.strivelearn.flink.window;
​
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
​
/**
 * 自定义TimeWindow
 * @author strivelearn
 * @version MyTimeWindow.java, 2023年01月20日
 */
public class MyTimeWindow {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataSourceWithSocket = executionEnvironment.socketTextStream("192.168.234.100", 9001);
        // 需要设置时间语义
        executionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
        dataSourceWithSocket.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] words = s.split(" ");
                for (String word : words) {
                    collector.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(0)
                            // 自定义窗口大小
                            .window(TumblingEventTimeWindows.of(Time.seconds(10))).sum(1).print();
        executionEnvironment.execute("MyTimeWindow");
    }
}
1.4、Window聚合
  1. 增量聚合

    窗口中每进入一条数据,就进行一次计算。

    常用函数:reduce、aggregate、sum、min、max

  2. 全量聚合

    等属于窗口的数据到齐,才开始进行聚合计算。(可以实现对窗口内的数据进行排序等需求)

    常用的全量函数:apply(windowFunction)、process(processWindowFunction)。processWindowFunction比windowFunction提供了更多的上下文信息

二、Time

  1. Event Time:事件产生的时间,通常由事件中的时间戳描述
  2. Ingestion Time:事件进入Flink的时间
  3. Processing Time:事件被处理时当前系统的时间

eg:原始日志:2023-01-01 10:00:00 产生。日志进入Flink的时间是 2023-01-01 20:00:01。日志数据到达Window处理的时间是:2023-01-01 20:00:02

如果我们想要统计每分钟内接口调用失败的错误日志个数,使用哪个时间才有意义?如果使用数据进入Flink的时间或者Window处理的时间,其实是没有意义的。这个时候,我们需要使用原始日志的时间才是有意义的。

在Flink的流处理中默认使用的是哪个时间呢?

默认情况下,Flink在流处理中使用的时间是Processing Time。如果想要修改的话,怎么修改呢?

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) or

env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)