Flink DataStream API(三)EventTime 与 Window

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/s294878304/article/details/102698464

7.1 EventTime 的引入

在 Flink 的 流 式 处 理中, 绝大 部 分 的 业务都 会 使 用 eventTime,一般只在eventTime 无法使用时,才会被迫使用 ProcessingTime 或者 IngestionTime。如果要使用 EventTime,那么需要引入 EventTime 的时间属性,引入方式如下所
示:

val env = StreamExecutionEnvironment.getExecutionEnvironment
// 从调用时刻开始给 env 创建的每一个 stream 追加时间特征
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

7.2.1 基本概念

我们知道,流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,虽然大部分情况下,流到 operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络等原因,导致乱序的产生,所谓乱序,就是指 Flink
接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的。

那么此时出现一个问题,一旦出现乱序,如果只根据 eventTime 决定 window 的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,此时必须要有个机制来保证一个特定的时间后,必须触发 window 去进行计算了,这个特别的机
制,就是 Watermark。

Watermark 是一种衡量 Event Time 进展的机制,它是数据本身的一个隐藏属性,数据本身携带着对应的 Watermark。

Watermark 是 用 于 处 理 乱 序 事 件 的 , 而 正 确 的 处 理 乱 序 事 件 , 通 常 用Watermark 机制结合 window 来实现。

数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,因此, window 的执行也是由 Watermark 触发的。

Watermark 可以理解成一个延迟触发机制,我们可以设置 Watermark 的延时时长 t,每次系统会校验已经到达的数据中最大的 maxEventTime,然后认定eventTime 小于 maxEventTime - t 的所有数据都已经到达,如果有窗口的停止时间等于 maxEventTime – t,那么这个窗口被触发执行。

有序流的 Watermarker 如下图所示:(Watermark 设置为 0)

乱序流的 Watermarker 如下图所示:(Watermark 设置为 2)
 

当 Flink 接收到每一条数据时,都会产生一条 Watermark,这条 Watermark就等于当前所有到达数据中的 maxEventTime - 延迟时长, 也就是说, Watermark是由数据携带的,一旦数据携带的 Watermark 比当前未触发的窗口的停止时间要晚,那么就会触发相应窗口的执行。由于 Watermark 是由数据携带的,因此,如果运行过程中无法获取新的数据,那么没有被触发的窗口将永远都不被触发。

上图中,我们设置的允许最大延迟到达时间为 2s,所以时间戳为 7s 的事件对应的 Watermark 是 5s,时间戳为 12s 的事件的 Watermark 是 10s,如果我们的窗口 1是 1s~5s,窗口 2 是 6s~10s,那么时间戳为 7s 的事件到达时的 Watermarker 恰好触发窗口 1,时间戳为 12s 的事件到达时的 Watermark 恰好触发窗口 2。
 

7.2.2 Watermark 的引入
 

    // 从调用时刻开始给 env 创建的每一个 stream 追加时间特征
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    val stream = env.socketTextStream("localhost", 11111).assignTimestampsAndWatermarks(
      new BoundedOutOfOrdernessTimestampExtractor[String](Time.milliseconds(200)) {
        override def extractTimestamp(t: String): Long = {
          // EventTime 是日志生成时间,我们从日志中解析 EventTime
          t.split(" ")(0).toLong
        }
      })

7.3 EvnetTimeWindow API

当使用 EventTimeWindow 时,所有的 Window 在 EventTime 的时间轴上进行划分,也就是说, 在 Window 启动后, 会根据初始的 EventTime 时间每隔一段时间划分一个窗口, 如果 Window 大小是 3 秒,那么 1 分钟内会把 Window 划分为如下的形式:

[00:00:00,00:00:03)
[00:00:03,00:00:06)
...
[00:00:57,00:01:00)

如果 Window 大小是 10 秒,则 Window 会被分为如下的形式:

[00:00:00,00:00:10)
[00:00:10,00:00:20)
...
[00:00:50,00:01:00)

注意,窗口是左闭右开的,形式为: [window_start_time,window_end_time)。Window 的设定无关数据本身,而是系统定义好了的,也就是说, Window 会一直按照指定的时间间隔进行划分,不论这个 Window 中有没有数据, EventTime 在这个 Window 期间的数据会进入这个 Window。

Window 会不断产生,属于这个 Window 范围的数据会被不断加入到 Window 中,所有未被触发的 Window 都会等待触发, 只要 Window 还没触发,属于这个 Window范围的数据就会一直被加入到 Window 中,直到 Window 被触发才会停止数据的追加,而当 Window 触发之后才接受到的属于被触发 Window 的数据会被丢弃。

Window 会在以下的条件满足时被触发执行:

  • watermark 时间 >= window_end_time;

  • 在[window_start_time,window_end_time)中有数据存在。

7.3.1 滚动窗口(TumblingEventTimeWindows)

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 创建 SocketSource
    val stream = env.socketTextStream("localhost", 11111)
    // 对 stream 进行处理并按 key 聚合
    val streamKeyBy = stream.assignTimestampsAndWatermarks(
      new BoundedOutOfOrdernessTimestampExtractor[String](Time.milliseconds(3000)) {
        override def extractTimestamp(element: String): Long = {
          val sysTime = element.split(" ")(0).toLong
          println(sysTime)
          sysTime
        }}).map(item => (item.split(" ")(1), 1)).keyBy(0)
    // 引入滚动窗口
    val streamWindow = streamKeyBy.window(TumblingEventTimeWindows.of(Time.seconds(10)))
    // 执行聚合操作
    val streamReduce = streamWindow.reduce(
      (item1, item2) => (item1._1, item1._2 + item2._2)
    )
    // 将聚合数据写入文件
    streamReduce.print
    // 执行程序
    env.execute("TumblingWindow")

结果是按照 Event Time 的时间窗口计算得出的,而无关系统的时间(包括输入的快慢) 。

7.3.2 滑动窗口(SlidingEventTimeWindows)
 

    // 获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 创建 SocketSource
    val stream = env.socketTextStream("localhost", 11111)
    // 对 stream 进行处理并按 key 聚合
    val streamKeyBy = stream.assignTimestampsAndWatermarks(
      new BoundedOutOfOrdernessTimestampExtractor[String](Time.milliseconds(0)) {
        override def extractTimestamp(element: String): Long = {
          val sysTime = element.split(" ")(0).toLong
          println(sysTime)
          sysTime
        }}).map(item => (item.split(" ")(1), 1)).keyBy(0)
    // 引入滚动窗口
    val streamWindow = streamKeyBy.window(SlidingEventTimeWindows.of(Time.seconds(10),
      Time.seconds(5)))
    // 执行聚合操作
    val streamReduce = streamWindow.reduce(
      (item1, item2) => (item1._1, item1._2 + item2._2)
    )
    // 将聚合数据写入文件
    streamReduce.print
    // 执行程序
    env.execute("TumblingWindow")

7.3.3 会话窗口(EventTimeSessionWindows)

相邻两次数据的 EventTime 的时间差超过指定的时间间隔就会触发执行。 如果加入 Watermark,那么当触发执行时,所有满足时间间隔而还没有触发的 Window 会同时触发执行。

    // 获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 创建 SocketSource
    val stream = env.socketTextStream("localhost", 11111)
    // 对 stream 进行处理并按 key 聚合
    val streamKeyBy = stream.assignTimestampsAndWatermarks(
      new BoundedOutOfOrdernessTimestampExtractor[String](Time.milliseconds(0)) {
        override def extractTimestamp(element: String): Long = {
          val sysTime = element.split(" ")(0).toLong
          println(sysTime)
          sysTime
        }}).map(item => (item.split(" ")(1), 1)).keyBy(0)
    // 引入滚动窗口
    val streamWindow = streamKeyBy.window(EventTimeSessionWindows.withGap(Time.seconds(5)))
    // 执行聚合操作
    val streamReduce = streamWindow.reduce(
      (item1, item2) => (item1._1, item1._2 + item2._2)
    )
    // 将聚合数据写入文件
    streamReduce.print
    // 执行程序
    env.execute("TumblingWindow")

猜你喜欢

转载自blog.csdn.net/s294878304/article/details/102698464