Flink07:Flink中的Window和Time详解

一、Window(窗口)

Flink 认为 批处理 是 流处理 的一个特例,所以 Flink 底层引擎是一个流式引擎,在上面实现了流处理和批处理。而Window就是从 流处理 到 批处理 的一个桥梁。
通常来讲,Window是一种可以把无界数据切割为有界数据块的手段

例如,对流中的所有元素进行计数是不可能的,因为通常流是无限的(无界的)。所以,流上的聚合需要由 Window 来划定范围,比如 “计算过去5分钟” 或者 “最后100个元素的和”。
window可以是时间驱动的 (Time Window)(比如:每30秒)或者数据驱动的(Count Window)(如每100个元素)。

DataStream API提供了基于Time和Count的Window。同时,由于某些特殊的需要,DataStream API也提供了定制化的Window操作,供用户自定义Window。

1、Window的类型

Window根据类型可以分为两种。

1、Tumbling Windows:滚动窗口,表示窗口内的数据没有重叠
2、Sliding Windows:滑动窗口,表示窗口内的数据有重叠

(1)tumbling window

在这里插入图片描述

(2)Sliding Windows

在这里插入图片描述

2、Window类型汇总

针对Window的类型关系进行一个汇总

在这里插入图片描述

(1)TimeWindow

TimeWindow 是根据时间对数据流切分窗口,TimeWindow可以支持滚动窗口和滑动窗口。

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

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

下面来演示一下
scala代码如下:

package com.imooc.scala.window

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time

/**
 * TimeWindow的使用
 * 1:滚动窗口
 * 2:滑动窗口
 * 
 */
object TimeWindowOpScala {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("bigdata04", 9001)

    import org.apache.flink.api.scala._


    //TimeWindow之滚动窗口:每隔10秒计算一次前10秒时间窗口内的数据
    /*text.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      //窗口大小
      .timeWindow(Time.seconds(10))
      .sum(1).print()*/

    //TimeWindow之滑动窗口:每隔5秒计算一次前10秒时间窗口内的数据
    text.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      //第一个参数:窗口大小,第二个参数:滑动间隔
      .timeWindow(Time.seconds(10),Time.seconds(5))
      .sum(1).print()

    env.execute("TimeWindowOpScala")

  }
}

在bigdata04上开启socket,输入hello you

[root@bigdata04 soft]# nc -l 9001
hello you

执行代码:
如果执行的是TimeWindow之滚动窗口代码,输出结果为:

5> (you,1)
3> (hello,1)

如果执行的是TimeWindow之滑动窗口代码,输出结果为:

5> (you,1)
3> (hello,1)
3> (hello,1)
5> (you,1)

java代码如下:

package com.imooc.java.window;

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
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;

/**
 * TimeWindow的使用
 * 1:滚动窗口
 * 2:滑动窗口
 * 
 */
public class TimeWindowOpJava {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> text = env.socketTextStream("bigdata04", 9001);

        //TimeWindow之滚动窗口:每隔10秒计算一次前10秒时间窗口内的数据
        /*text.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out)
                    throws Exception {
                String[] words = line.split(" ");
                for (String word: words) {
                    out.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).keyBy(0)
                //窗口大小
                .timeWindow(Time.seconds(10))
                .sum(1)
                .print();*/

        //TimeWindow之滑动窗口:每隔5秒计算一次前10秒时间窗口内的数据
        text.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out)
                    throws Exception {
                String[] words = line.split(" ");
                for (String word: words) {
                    out.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).keyBy(0)
                //第一个参数:窗口大小,第二个参数:滑动间隔
                .timeWindow(Time.seconds(10),Time.seconds(5))
                .sum(1)
                .print();

        env.execute("TimeWindowOpJava");
    }
}

(2)CountWindow

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

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

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

下面来演示一下

scala代码如下:

package com.imooc.scala.window

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time

/**
 * CountWindow的使用
 * 1:滚动窗口
 * 2:滑动窗口
 * 
 */
object CountWindowOpScala {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("bigdata04", 9001)

    import org.apache.flink.api.scala._

    /**
     * 注意:由于我们在这里使用了keyBy,会先对数据分组
     * 如果某个分组对应的数据窗口内达到了5个元素,这个窗口才会被触发执行
     */
    //CountWindow之滚动窗口:每隔5个元素计算一次前5个元素
    /*text.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      //指定窗口大小
      .countWindow(5)
      .sum(1).print()*/

    //CountWindow之滑动窗口:每隔1个元素计算一次前5个元素
    text.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      //第一个参数:窗口大小,第二个参数:滑动间隔
      .countWindow(5,1)
      .sum(1).print()

    env.execute("CountWindowOpScala")
  }
}

通过socket输入数据

[root@bigdata04 soft]# nc -l 9001
hello you
hello me
hello hello hello
you you you you
hello
you

执行CountWindow之滚动窗口的代码,输入结果如下:

3> (hello,5)
5> (you,5)

执行CountWindow之滑动窗口的代码,输入结果如下:

5> (you,1)
3> (hello,1)
4> (me,1)
3> (hello,2)
3> (hello,3)
3> (hello,4)
3> (hello,5)
5> (you,2)
5> (you,3)
5> (you,4)
5> (you,5)
3> (hello,5)
5> (you,5)

java代码如下:

package com.imooc.java.window;

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.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

/**
 * CountWindow的使用
 * 1:滚动窗口
 * 2:滑动窗口
 * 
 */
public class CountWindowOpJava {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> text = env.socketTextStream("bigdata04", 9001);

        //CountWindow之滚动窗口:每隔5个元素计算一次前5个元素
        text.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out)
                    throws Exception {
                String[] words = line.split(" ");
                for (String word: words) {
                    out.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).keyBy(0)
                //窗口大小
                .countWindow(5)
                .sum(1)
                .print();

        //CountWindow之滑动窗口:每隔1个元素计算一次前5个元素
        text.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out)
                    throws Exception {
                String[] words = line.split(" ");
                for (String word: words) {
                    out.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).keyBy(0)
                //第一个参数:窗口大小,第二个参数:滑动间隔
                .countWindow(5,1)
                .sum(1)
                .print();

        env.execute("CountWindowOpJava");
    }
}

(3)自定义Window

下面我们来看一下自定义window
其实window还可以再细分一下

1、一种是基于Key的Window
2、一种是不基于Key的Window

咱们前面演示的都是基于key的window,就是在使用window之前,先执行了keyBy分组操作,如果需求中不需要根据key进行分组的话,可以不使用keyBy,这样在使用。

window的时候需要使用timeWindowAll()和countWindowAll()。

如果是自定义window的话该如何使用呢?

在这里插入图片描述
针对基于key的window需要使用window函数。
针对不基于key的window需要使用windowAll函数。

其实我们前面所说的TimeWindow和TimeWindowAll底层用的就是window和windowAll函数,可以这样理解,TimeWindow是官方封装好的window。
以TimeWindow为例,看一下源码。

在这里插入图片描述
TimeWindowAll的源码是这样的。
在这里插入图片描述
那下面我们来试一下自己使用window函数封装一个MyTimeWindow

scala代码如下:

package com.imooc.scala.window

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

/**
 * 需求:自定义MyTimeWindow
 * 、
 */
object MyTimeWindowScala {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.socketTextStream("bigdata04", 9001)

    import org.apache.flink.api.scala._
    //自定义MyTimeWindow滚动窗口:每隔10秒计算一次前10秒时间窗口内的数据
    text.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      //窗口大小
      .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .sum(1).print()

    env.execute("MyTimeWindowScala")
  }
}

java代码如下:

package com.imooc.java.window;

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.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

/**
 * 需求:自定义MyTimeWindow
 * 
 */
public class MyTimeWindowScala {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> text = env.socketTextStream("bigdata04", 9001);

        //自定义MyTimeWindow滚动窗口:每隔10秒计算一次前10秒时间窗口内的数据
        text.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out)
                    throws Exception {
                String[] words = line.split(" ");
                for (String word: words) {
                    out.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).keyBy(0)
                //窗口大小
                .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
                .sum(1)
                .print();

        env.execute("MyTimeWindowScala");
    }
}

二、Window聚合

在进行Window聚合操作的时候可以分为两种

1、一种是增量聚合
2、一种是全量聚合

1、Window聚合之增量聚合

增量聚合:窗口中每进入一条数据,就进行一次计算
常见的一些增量聚合函数如下:
reduce()、aggregate()、sum()、min()、max()

下面我们来看一个增量聚合的例子,累加求和,对 8 、12、7、10这四条数据进行累加求和:

第一次进来一条数据8,则立刻进行累加求和,结果为8。
第二次进来一条数据12,则立刻进行累加求和,结果为20。
第三次进来一条数据7,则立刻进行累加求和,结果为27。
第四次进来一条数据10,则立刻进行累加求和,结果为37。

在这里插入图片描述
再来看一下Reduce函数的使用
在这里插入图片描述
从这里面可以看出来reduce是每次获取一条数据,和上一次的执行结果求和,也就是来一条数据立刻计算一次。

这就是增量聚合。

2、Window聚合之全量聚合

全量聚合:等属于窗口的数据到齐,才开始进行聚合计算【可以实现对窗口内的数据进行排序等需求】
常见的一些全量聚合函数为apply(windowFunction)和process(processWindowFunction)

注意:processWindowFunction比windowFunction提供了更多的Context(上下文)信息。

下面我们来看一个全量聚合的例子,求最大值,对8 、12、7、10这四条数据求最大值
第一次进来一条数据8。
第二次进来一条数据12。
第三次进来一条数据7。
第四次进来一条数据10,此时窗口触发,才会对窗口内的数据进行排序,获取最大值。

在这里插入图片描述
下面来看一下apply函数的使用

在这里插入图片描述
还有process的使用
在这里插入图片描述
在这我们会发现,这些全量聚合的函数获取到的输入数据是一个Iterable,里面是包含多条数据的,从这可以看出来,这两个函数是一次性获取一个窗口内的所有数据进行计算的。

三、Time

针对流数据中的Time(时间),可以分为以下3种。
Event Time:事件产生的时间,它通常由事件中的时间戳描述。
Ingestion time:事件进入Flink的时间。
Processing Time:事件被处理时当前系统的时间

这几种时间的对应关系通过这个图可以很清晰的看出来

在这里插入图片描述

1、Time案例分析

举个例子:

原始日志是这样的:2026-01-01 10:00:01 INFO executor.Executor: Finished task in state 0.0

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

如果我们想要统计每分钟内接口调用失败的错误日志个数,使用哪个时间才有意义?

因为数据有可能会出现延迟,如果使用 数据进入Flink的时间 或者 Window处理的时间,其实是没有意义的,这个时候需要使用原始日志中的时间才是有意义的,这个才是数据产生的时间。

2、Time类型设置

那我们在Flink的流处理中,默认使用的是哪个时间呢?
默认情况下Flink在流处理中使用的时间是ProcessingTime
查看源码:在类StreamExecutionEnvironment中

那我们在Flink的流处理中,默认使用的是哪个时间呢?
默认情况下Flink在流处理中使用的时间是ProcessingTime
查看源码:在类StreamExecutionEnvironment中

private static final TimeCharacteristic DEFAULT_TIME_CHARACTERISTIC = TimeCharacteristic.ProcessingTime;

如何修改呢?
想要修改的话可以使用setStreamTimeCharacteristic(…)

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

或者

env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做一个有趣的人Zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值