Flink:四大基石[Time,Window,Checkpoint,State]

Time 与 Window

Time:

在 Flink 的流式处理中,会涉及到时间的不同概念,如下图所示:在这里插入图片描述
Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。
Ingestion Time:是数据进入 Flink 的时间。

Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time。
例如:
一条日志进入 Flink 的时间为 2019-08-12 10:00:00.123,到达 Window 的系统时间为2019-08-12 10:00:01.234
日志的内容如下:
2019-08-02 18:37:15.624 INFO Fail over to rm2
对于业务来说,要统计 1min 内的故障日志个数,哪个时间是最有意义的?—— eventTime
因为我们要根据日志的生成时间进行统计

Window

Window 概述

streaming 流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而 window 是一种切割无限数据为有限块进行处理的手段。Window 是无限数据流处理的核心,Window 将一个无限的 stream 拆分成有限大小的”buckets”桶,我们可以在这些桶上做计算操作。

Window 类型

Window 可以分成两类:

  1. CountWindow:按照指定的数据条数生成一个 Window,与时间无关。

  2. TimeWindow:按照时间生成 Window。
    对于 TimeWindow,可以根据窗口实现原理的不同分成三类:
    滚动窗口 [Tumbling Window]
    滑动窗口 [Sliding Window]
    会话窗口 [Session Window]

滚动窗口 [Tumbling Window] :

将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,
滚动窗口有一个固定的大小,并且不会出现重叠。
例如:
如果你指定了一个 5 分钟大小的滚动窗口,窗口的创建如下图所示:
在这里插入图片描述
适用场景:适合做 BI 统计等(做每个时间段的聚合计算)。

滑动窗口 [Sliding Window] :

滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成
特点:时间对齐,窗口长度固定,有重叠
滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,
窗口的大小由窗口大小 参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。
因此,滑动窗口如果滑动参数小于 窗口大小的话,窗口是可以重叠的,
在这种情况下元素会被分配到多个窗口中。
例如,你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个
10分钟产生的数据。
如下图所示:在这里插入图片描述
适用场景:对最近一个时间段内的统计(求某接口最近 5min 的失败率来决定是否要报警)。

会话窗口 [Session Window] :

由一系列事件组合一个指定时间长度的 timeout 间隙组成,
类似于 web 应用的 session,也 就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
session 窗口分配器通过 session 活动来对元素进行分组,session 窗口跟滚动窗口和滑动 窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,
当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭
一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。在这里插入图片描述

Window API

CountWindow

CountWindow 根据窗口中相同 key 元素的数量来触发执行,执行时只计算元素数量达到窗口大小的 key 对应的结果。
注意:CountWindow 的 window_size 指的是相同 Key 的元素的个数,不是输入的所有元素的总数。

  1. 滚动窗口
    默认的 CountWindow 是一个滚动窗口,只需要指定窗口大小即可,当元素数量达到窗口大小时,就会触发窗口的执行。

步骤

  1. 获取执行环境

  2. 创建 SocketSource

  3. 对 stream 进行处理并按 key 聚合

  4. countWindow 操作

  5. 执行聚合操作

  6. 将聚合数据输出

  7. 执行程序

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.windows.GlobalWindow

/**
 * 思路步骤:
 * 1.获取执行环境
 * 2.创建 SocketSource
 * 3.对 stream 进行处理并按 key 聚合
 * 4.countWindow 操作
 * 5.执行聚合操作
 * 6.将聚合数据输出
 * 7.执行程序
 */
object StreamCountWindow {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    //2.构建数据源,创建 SocketSource
    val socketSource: DataStream[String] = env.socketTextStream("node01",9999)

    //3.对 stream 进行处理并按 key 聚合
    import org.apache.flink.api.scala._
    val groupKeyedStream: KeyedStream[(String, Int), String] = socketSource.flatMap(x=>x.split(" ")).map((_,1)).keyBy(_._1)

    //4.引入countWindow 操作,每5条数据计算一次
    val countWindowStream: WindowedStream[(String, Int), String, GlobalWindow] = groupKeyedStream.countWindow(5)

    //5.执行聚合操作
    val resultDataStream: DataStream[(String, Int)] = countWindowStream.sum(1)

    //6.将聚合数据输出
    resultDataStream.print()

    //7.执行程序
    env.execute("StreamCountWindow")
  }

}

TimeWindow

TimeWindow 是将指定时间范围内的所有数据组成一个 window,一次对一个 window 里面的所有数据进行计算。

滚动窗口

Flink 默认的时间窗口根据 Processing Time 进行窗口的划分,将 Flink 获取到的数据根据进入 Flink 的时间划分到不同的窗口中。

步骤

  1. 获取执行环境

  2. 创建你 socket 链接获取数据

  3. 进行数据转换处理并按 key 聚合

  4. 引入 timeWindow

  5. 执行聚合操作

  6. 输出打印数据

  7. 执行程序

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

/**
 * 思路步骤:
 * 1.获取执行环境
 * 2.创建你 socket 链接获取数据
 * 3.进行数据转换处理并按 key 聚合
 * 4.引入 timeWindow
 * 5.执行聚合操作
 * 6.输出打印数据
 * 7.执行程序
 */
object StreamTimeWindow {
  def main(args: Array[String]): Unit = {
    //1.获取执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.创建你 socket 链接获取数据
    val socketSource = env.socketTextStream("node01",9999)
    //3.进行数据转换处理并按 key 聚合
    import org.apache.flink.api.scala._
    val groupKeyedStream: KeyedStream[(String, Int), String] = socketSource.flatMap(x=>x.split(" ")).map((_,1)).keyBy(_._1)
    //4.引入 滚动窗口timeWindow,每3秒钟计算一次
    val timeWindowStream: WindowedStream[(String, Int), String, TimeWindow] = groupKeyedStream.timeWindow(Time.seconds(3))
    //5.执行聚合操作
    val result: DataStream[(String, Int)] = timeWindowStream.sum(1)
    //6.打印输出
    result.print()
    //7.执行程序
    env.execute("StreamTimeWindow")
  }
}

滑动窗口

Flink 默认的时间窗口根据 Processing Time 进行窗口的划分,将 Flink 获取到的数据根据进入 Flink 的时间划分到不同的窗口中。

步骤

  1. 获取执行环境

  2. 创建你 socket 链接获取数据

  3. 进行数据转换处理并按 key 聚合

  4. 引入 timeWindow

  5. 执行聚合操作

  6. 输出打印数据

  7. 执行程序

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

/**
 * 思路步骤:
 * 1.获取执行环境
 * 2.创建你 socket 链接获取数据
 * 3.进行数据转换处理并按 key 聚合
 * 4.引入 timeWindow
 * 5.执行聚合操作
 * 6.输出打印数据
 * 7.执行程序
 */
object StreamTimeWindow {
  def main(args: Array[String]): Unit = {
    //1.获取执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.创建你 socket 链接获取数据
    val socketSource = env.socketTextStream("node01",9999)
    //3.进行数据转换处理并按 key 聚合
    import org.apache.flink.api.scala._
    val groupKeyedStream: KeyedStream[(String, Int), String] = socketSource.flatMap(x=>x.split(" ")).map((_,1)).keyBy(_._1)
    //4.引入 滑动窗口timeWindow,窗口大小为10秒,滑动距离为5秒=>重复消费
    val timeWindowStream: WindowedStream[(String, Int), String, TimeWindow] = groupKeyedStream.timeWindow(Time.seconds(10),Time.seconds(5))
    //5.执行聚合操作
    val result: DataStream[(String, Int)] = timeWindowStream.sum(1)
    //6.打印输出
    result.print()
    //7.执行程序
    env.execute("StreamTimeWindow")
  }
}

Window Reduce

WindowedStream → DataStream:给 window 赋一个 reduce 功能的函数,并返回一个聚合的结果。

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

object StreamReduceWindow {
  def main(args: Array[String]): Unit = {
    //1.获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //2.构建数据集
    val socketSource = env.socketTextStream("node01",9999)
    //3.分组
    import org.apache.flink.api.scala._
    val group = socketSource.flatMap(x=>x.split(" ")).map((_,1)).keyBy(_._1)

    //4.引入窗口timeWindow
    val timeWindow: WindowedStream[(String, Int), String, TimeWindow] = group.timeWindow(Time.seconds(5))

    //5.聚合操作
    val result: DataStream[(String, Int)] = timeWindow.reduce((v1, v2)=>(v1._1,v1._2+v2._2))

    //6.输出打印
    result.print()
    //7.执行程序
    env.execute()

  }
}

Window Apply

apply 方法可以进行一些自定义处理,通过匿名内部类的方法来实现。当有一些复杂计算时使用。

用法

  1. 实现一个 WindowFunction 类

  2. 指定该类的泛型为 [输入数据类型, 输出数据类型, keyBy 中使用分组字段的类型, 窗口类型]

示例

使用 apply 方法来实现单词统计

步骤

  1. 获取流处理运行环境

  2. 构建 socket 流数据源,并指定 IP 地址和端口号

  3. 对接收到的数据转换成单词元组

  4. 使用 keyBy 进行分流(分组)

  5. 使用 timeWinodw 指定窗口的长度(每 3 秒计算一次)

  6. 实现一个 WindowFunction 匿名内部类

    6.a.apply 方法中实现聚合计算

    6.b.使用 Collector.collect 收集数据

  7. 打印输出

  8. 启动执行

  9. 在 Linux 中,使用 nc -lk 端口号 监听端口,并发送单词

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.function.RichWindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

//使用 apply 方法来实现单词统计
object StreamApplyWindow {
  def main(args: Array[String]): Unit = {
    /**
     * 思路步骤:
     * 1) 获取流处理运行环境
     * 2) 构建 socket 流数据源, 并指定 IP 地址和端口号
     * 3) 对接收到的数据转换成单词元组
     * 4) 使用 keyBy 进行分流( 分组)
     * 5) 使用 timeWindow 指定窗口的长度( 每 3 秒计算一次)
     * 6) 实现一个 WindowFunction 匿名内部类
     * a. apply 方法中实现聚合计算
     * b. 使用 Collector.collect 收集数据
     * 7) 打印输出
     * 8) 启动执行
     * 9) 在 Linux 中, 使用 nc -lk 端口号 监听端口, 并发送单词
     */

    //1.获取流处理运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2. 构建 socket 流数据源, 并指定 IP 地址和端口号
    val socketSource: DataStream[String] = env.socketTextStream("node01",9999)
    //3.对接收到的数据转换成单词元组
    import org.apache.flink.api.scala._
    val wordAndOne: DataStream[(String, Int)] = socketSource.flatMap(x=>x.split(" ")).map((_,1))
    //4.使用 keyBy 进行分流( 分组)
    val groupKeyedStream: KeyedStream[(String, Int), String] = wordAndOne.keyBy(_._1)
    //5.使用 timeWindow 指定窗口的长度( 每 3 秒计算一次)
    val timeWindow: WindowedStream[(String, Int), String, TimeWindow] = groupKeyedStream.timeWindow(Time.seconds(3))
    //6.实现一个 WindowFunction 匿名内部类
    val result: DataStream[(String, Int)] = timeWindow.apply(new RichWindowFunction[(String, Int), (String, Int), String, TimeWindow] {
      override def apply(key: String, window: TimeWindow, input: Iterable[(String, Int)], out: Collector[(String, Int)]): Unit = {
        //apply 方法中实现聚合计算
        val tuple: (String, Int) = input.reduce((v1, v2) => (v1._1, v1._2 + v2._2))
        //使用 Collector.collect 收集数据
        out.collect(tuple)
      }
    })
    //7.打印输出
    result.print()
    //8.执行程序
    env.execute("StreamApplyWindow")
  }
}

Window Fold

WindowedStream → DataStream:给窗口赋一个 fold 功能的函数,并返回一个 fold 后的结果。

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
object StreamFoldWindow {
  def main(args: Array[String]): Unit = {
    // 获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 创建 SocketSource
    val stream = env.socketTextStream("node01", 9999)
    // 对 stream 进行处理并按 key 聚合
    val streamKeyBy: KeyedStream[(String, Int), String] = stream.flatMap(x => x.split(" ")).map((_, 1)).keyBy(_._1)
    // 引入滚动窗口,每3秒计算一次
    val timeWindow: WindowedStream[(String, Int), String, TimeWindow] = streamKeyBy.timeWindow(Time.seconds(3))
    // 执行 fold 操作
    val result: DataStream[Int] = timeWindow.fold(100) {
      (begin, item) => begin + item._2
    }
    result.print()
    env.execute("StreamFoldWindow")
  }
}

Aggregation on Window

WindowedStream → DataStream:对一个 window 内的所有元素做聚合操作。min 和 minBy 的区别是 min 返回的是最小值,而 minBy 返回的是包含最小值字段的元素(同样的原理适用于 max 和 maxBy)。

package com.czxy.flink.stream.window

import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

object StreamAggregationWindow {
  def main(args: Array[String]): Unit = {
    //1.获取流处理运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2. 构建 socket 流数据源, 并指定 IP 地址和端口号
    val socketSource: DataStream[String] = env.socketTextStream("node01",9999)
    //3.对接收到的数据转换成单词元组
    import org.apache.flink.api.scala._
    val wordAndOne: DataStream[(String, Int)] = socketSource.flatMap(x=>x.split(" ")).map((_,1))
    //4.使用 keyBy 进行分流( 分组)
    val groupKeyedStream: KeyedStream[(String, Int), String] = wordAndOne.keyBy(_._1)
    //5.使用 timeWindow 指定窗口的长度( 每 3 秒计算一次)
    val timeWindow: WindowedStream[(String, Int), String, TimeWindow] = groupKeyedStream.timeWindow(Time.seconds(3))
    //6.执行聚合操作
    val result: DataStream[(String, Int)] = timeWindow.max(1)
    //7.打印输出
    result.print()
    //8.执行程序
    env.execute(this.getClass.getSimpleName)

  }
}

EventTime 与 Window

EventTime 的引入

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

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

Watermark

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

在这里插入图片描述
那么此时出现一个问题,一旦出现乱序,如果只根据 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。

Watermark 的引入

val env = StreamExecutionEnvironment.getExecutionEnvironment

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

EventTimeWindow 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)中有数据存在。

我们通过下图来说明 Watermark、EventTime 和 Window 的关系。
在这里插入图片描述

滚动窗口(TumblingEventTimeWindows)

package com.czxy.flink.stream.waterwindow

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

object TumblingEventTimeWindowsDemo {
  def main(args: Array[String]): Unit = {
    /**
     * 步骤:
     * 1.创建流处理环境
     * 2.设置EventTime
     * 3.构建数据源
     * 4.设置水印
     * 5.逻辑处理
     * 6.引入滚动窗口TumblingEventTimeWindows
     * 7.聚合操作
     * 8.输出打印
     * 9.执行程序
     */
    //1.创建流处理环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.设置EventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //3.构建数据源
    //数据格式: 1000 hello
    val socketSource = env.socketTextStream("node01",9999)
    //4.设置水印
    val waterMark: DataStream[String] = socketSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[String](Time.seconds(0)) {
      override def extractTimestamp(element: String): Long = {
        val eventTime: Long = element.split(" ")(0).toLong
        eventTime
      }
    })
    //5.逻辑处理
    import org.apache.flink.api.scala._
    val groupStream: KeyedStream[(String, Int), String] = waterMark.map(x=>x.split(" ")(1)).map((_,1)).keyBy(_._1)
    //6.引入滚动窗口TumblingEventTimeWindows
    val windowStream: WindowedStream[(String, Int), String, TimeWindow] = groupStream.window(TumblingEventTimeWindows.of(Time.seconds(3)))
    //7.聚合操作
    val result: DataStream[(String, Int)] = windowStream.sum(1)
    //8.输出打印
    result.print()
    //9.执行程序
    env.execute(this.getClass.getSimpleName)
  }
}

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

滑动窗口(SlidingEventTimeWindows)

package com.czxy.flink.stream.waterwindow

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

object SlidingEventTimeWindowsDemo {
  def main(args: Array[String]): Unit = {
    /** * 步骤:
     * 1.创建流处理环境
     * 2.设置EventTime
     * 3.构建数据源
     * 4.设置水印
     * 5.逻辑处理
     * 6.引入滑动窗口SlidingEventTimeWindows
     * 7.聚合操作
     * 8.输出打印
     * 9.执行程序
     */

    //1.创建流处理环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.设置EventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //3.构建数据源
    //数据格式为:1000 hello
    val socketSource: DataStream[String] = env.socketTextStream("node01", 9999)
    //4.设置水印
    val waterMark: DataStream[String] = socketSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[String](Time.seconds(0)) {
      override def extractTimestamp(element: String): Long = {
        val eventTime: Long = element.split(" ")(0).toLong
        eventTime
      }
    })
    //5.逻辑处理
    import org.apache.flink.api.scala._
    val groupStream: KeyedStream[(String, Int), String] = waterMark.map(x => x.split(" ")(1)).map((_, 1)).keyBy(_._1)
    //6.引入滑动窗口SlidingEventTimeWindows
    val windowStream: WindowedStream[(String, Int), String, TimeWindow] = groupStream.window(SlidingEventTimeWindows.of(Time.seconds(5), Time.seconds(2)))
    //7.聚合计算
    val result: DataStream[(String, Int)] = windowStream.sum(1)
    //8.打印输出
    result.print()
    env.execute(this.getClass.getSimpleName)

  }
}

会话窗口(EventTimeSessionWindows)

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

package com.czxy.flink.stream.waterwindow

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, KeyedStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow

object EventTimeSessionWindowsDemo {
  def main(args: Array[String]): Unit = {
    /**
     * 步骤:
     * 1.创建流处理环境
     * 2.设置EventTime
     * 3.构建数据源
     * 4.设置水印
     * 5.逻辑处理
     * 6.引入会话窗口EventTimeSessionWindows
     * 7.聚合操作
     * 8.输出打印
     */
    //1.创建流处理环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.设置EventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //3.构建数据源
    //数据格式为:1000 hello
    val socketSource: DataStream[String] = env.socketTextStream("node01", 9999)
    //4.设置水印
    val waterMark: DataStream[String] = socketSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[String](Time.seconds(0)) {
      override def extractTimestamp(element: String): Long = {
        val eventTime: Long = element.split(" ")(0).toLong
        eventTime
      }
    })
    //5.逻辑处理
    import org.apache.flink.api.scala._
    val groupStream: KeyedStream[(String, Int), String] = waterMark.map(x => x.split(" ")(1)).map((_, 1)).keyBy(_._1)
    //6.引入会话窗口EventTimeSessionWindows
    val windowStream: WindowedStream[(String, Int), String, TimeWindow] = groupStream.window(EventTimeSessionWindows.withGap(Time.seconds(3)))
    //7.聚合计算
    val result = windowStream.sum(1)
    //8.打印输出
    result.print()
    //9.执行程序
    env.execute("EventTimeSessionWindowsDemo")


  }
}

Flink 的容错

Checkpoint 介绍

Checkpoint 是 Flink 实现容错机制最核心的功能。
也是 flink 的四大基石之一


它能够根据配置周期性地基于 Stream 中各个 Operator/task 的状态来生成快照,从而将这些状态数据定期 持久化存储下来,当 Flink 程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行 恢复,从而修正因为故障带来的程序数据异常。


它能够根据配置周期性地基于 Stream 中各个 Operator/task 的状态来生成快照,从而将这些状态数据定期 持久化存储下来,当 Flink 程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行 恢复,从而修正因为故障带来的程序数据异常。


它能够根据配置周期性地基于 Stream 中各个 Operator/task 的状态来生成快照,从而将这些状态数据定期 持久化存储下来,当 Flink 程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行 恢复,从而修正因为故障带来的程序数据异常。


每 个 需 要 checkpoint 的 应 用 在 启 动 时 , Flink 的 JobManager 为 其 创建 一 个 CheckpointCoordinator(检查点协调器),CheckpointCoordinator 全权负责本应用的快照制作。
在这里插入图片描述

  1. CheckpointCoordinator 周期性的向该流应用的所有 source 算子发送 barrier。

  2. 当某个 source 算子收到一个 barrier 时,便暂停数据处理过程,然后将自己的当前状态制作成快照,并保存到指定的持久化存储中,最后向 CheckpointCoordinator 报告 自己快照制作情况,同时向自身所有下游算子广播该 barrier,恢复数据处理 。

  3. 下游算子收到 barrier 之后,会暂停自己的数据处理过程,然后将自身的相关状态制作成快照,并保存到指定的持久化存储中,最后向 CheckpointCoordinator 报告自身 快照情况,同时向自身所有下游算子广播该 barrier,恢复数据处理。

  4. 每个算子按照步骤 3 不断制作快照并向下游广播,直到最后 barrier 传递到 sink 算子,快照制作完成。

  5. 当 CheckpointCoordinator 收到所有算子的报告之后,认为该周期的快照制作成功; 否则,如果在规定的时间内没有收到所有算子的报告,则认为本周期快照制作失败。
    如果一个算子有两个输入源,则暂时阻塞先收到 barrier 的输入源,等到第二个输入
    源相同编号的 barrier 到来时,再制作自身快照并向下游广播该 barrier。具体如下图所示两个输入源 checkpoint 过程:
    在这里插入图片描述

  • 假设算子 C 有 A 和 B 两个输入源

  • 在第 i 个快照周期中,由于某些原因(如处理时延、网络时延等)输入源 A 发出的 barrier 先到来,这时算子 C 暂时将输入源 A 的输入通道阻塞,仅收输入源 B 的数据。

  • 当 输 入 源 B 发 出 的 barrier 到 来 时 , 算 子 C 制 作 自 身 快 照 并 向 CheckpointCoordinator 报告自身的快照制作情况,然后将两个 barrier 合并为一个,向下游所有的算子广播。

  • 当由于某些原因出现故障时,CheckpointCoordinator 通知流图上所有算子统一恢复到某个周期的 checkpoint 状态,然后恢复数据流处理。分布式 checkpoint 机制保证了数据仅被处理一次(Exactly Once)。

持久化存储

目前,Checkpoint 持久化存储可以使用如下三种:

MemStateBackend(默认)

该持久化存储主要将快照数据保存到 JobManager 的内存中,仅适合作为测试以及快照的数据量非常小时使用,并不推荐用作大规模商业部署。
MemoryStateBackend 的局限性(建议不要使用):
默认情况下,每个状态的大小限制为 5 MB。可以在 MemoryStateBackend 的构造函数中增加此值。无论配置的最大状态大小如何,状态都不能大于 akka 帧的大小(请参阅配置)。聚合状态必须适合 JobManager 内存。
建议 MemoryStateBackend 用于:
本地开发和调试状态很少的作业,例如仅包含一次记录功能的作业(Map,FlatMap,Filter,…),kafka 的消费者需要很少的状态。
注意:建议不要将快照保存到内存中,因为会导致数据丢失或者 jobmanager 服务器的压力增加

FsStateBackend(建议使用)

该持久化存储主要将快照数据保存到文件系统中,目前支持的文件系统主要是 HDFS 和本地文件。如果使用 HDFS,则初始化 FsStateBackend 时,需要传入以 “hdfs://”开头的路径(即: new FsStateBackend(“hdfs:///hacluster/checkpoint”)), 如果使用本地文件,则需要传入以 “file://”开头的路径(即:new FsStateBackend(“file:///Data”))。在分布式情况下,不推荐使用本地文件。如果某 个算子在节点 A 上失败,在节点 B 上恢复,使用本地文件时,在 B 上无法读取节点 A 上的数据,导致状态恢复失败。建议 FsStateBackend:
具有大状态,长窗口,大键 / 值状态的作业。
所有高可用性设置。

RocksDBStateBackend

RocksDBStatBackend 介于本地文件和 HDFS 之间,平时使用 RocksDB 的功能,将数据持久化 到本地文件中,当制作快照时,将本地数据制作成快照,并持久化到 FsStateBackend 中 (FsStateBackend 不必用户特别指明,只需在初始化时传入 HDFS 或本地 路 径 即 可 , 如 new RocksDBStateBackend(“hdfs:///hacluster/checkpoint”) 或newRocksDBStateBackend(“file:///Data”))。

如果用户使用自定义窗口(window),不推荐用户使用 RocksDBStateBackend。在自 定义窗口中,状态以 ListState 的形式保存在 StatBackend 中,如果一个 key 值中有多 个 value 值,则 RocksDB 读取该种 ListState 非常缓慢,影响性能。用户可以根据应用 的具体情况选择 FsStateBackend+HDFS 或 RocksStateBackend+HDFS。

语法

val env = StreamExecutionEnvironment.getExecutionEnvironment()
//start a checkpoint every 1000 ms env.enableCheckpointing(1000)
//advanced options:
//设置 checkpoint 的执行模式,最多执行一次或者至少执行一次 env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
//设置 checkpoint 的超时时间
env.getCheckpointConfig.setCheckpointTimeout(60000)
//如果在只做快照过程中出现错误,是否让整体任务失败:true 是 false 不是 env.getCheckpointConfig.setFailTasksOnCheckpointingErrors(false) //设置同一时间有多少 个 checkpoint 可以同时执行 env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)

修改 State Backend 的两种方式

第一种:单任务调整

修改当前任务代码
env.setStateBackend(new
FsStateBackend("hdfs://node01:8020/flink/checkpoints"));
或者 new MemoryStateBackend()
或者 new RocksDBStateBackend(filebackend, true);【需要添加第三方依赖】

第二种:全局调整

修改 flink-conf.yaml
state.backend: filesystem
state.checkpoints.dir: hdfs://namenode:9000/flink/checkpoints
注意:state.backend          
jobmanager(MemoryStateBackend),
filesystem(FsStateBackend),
rocksdb(RocksDBStateBackend)
导入依赖
<dependency>
<groupId>org.apache.flink</groupId> 
<artifactId>flink-statebackend-rocksdb_2.11</artifactId> <version>1.7.2</version>
</dependency>

Checkpoint 的高级选项

默认 checkpoint 功能是 disabled 的,想要使用的时候需要先启用 checkpoint 开启之后,
默认的 checkPointMode  Exactly-once
//配置一秒钟开启一个  checkpoint
env.enableCheckpointing(1000)
//指定  checkpoint 的执行模式
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
>CheckpointingMode.EXACTLY_ONCE:默认值
>CheckpointingMode.AT_LEAST_ONCE:至少一次
>一般情况下选择 CheckpointingMode.EXACTLY_ONCE,除非场景要求极低的延迟(几毫秒)
>注意:如果需要保证 EXACTLY_ONCE,source  sink 要求必须同时保证 EXACTLY_ONCE
------------------------------------------------------------------------------
------------------------------------------------------------------------------
//如果程序被  cancle,保留以前做的  checkpoint
env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
默认情况下,检查点不被保留,仅用于在故障中恢复作业,可以启用外部持久化检查点,同
时指定保留策略:
>ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:在作业取消时保留检查点,
>注意,在这种情况下,您必须在取消后手动清理检查点状态
>ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:当作业在被 cancel 时,
>删除检查点,检查点仅在作业失败时可用
------------------------------------------------------------------------------
------------------------------------------------------------------------------
//设置 checkpoint 超时时间
env.getCheckpointConfig.setCheckpointTimeout(60000)
Checkpointing 的超时时间,超时时间内没有完成则被终止
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500)
Checkpointing 最小时间间隔,用于指定上一个 checkpoint 
完成之后最小等多久可以触发另一个
checkpoint,当指定这个参数时,maxConcurrentCheckpoints 的值为 1
------------------------------------------------------------------------------
------------------------------------------------------------------------------
//设置同一个时间是否可以有多个  checkpoint 执行
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
指定运行中的 checkpoint 最多可以有多少个	
------------------------------------------------------------------------------
------------------------------------------------------------------------------
env.getCheckpointConfig.setFailOnCheckpointingErrors(true)
用于指定在 checkpoint 发生异常的时候,是否应该 fail  task,
默认是 true,如果设置为 false,则 task 会拒绝 checkpoint 然后继续运行

Flink 的重启策略

Flink 支持不同的重启策略, 这些重启策略控制着 job 失败后如何重启。 集群可以通过默认的重启策略来重启, 这个默认的重启策略通常在未指定重启策略的情况下使用,而如果 Job 提交的时候指定了重启策略, 这个重启策略就会覆盖掉集群的默认重启策略。

默认的重启策略是通过 Flink 的 flink-conf.yaml 来指定的, 这个配置参数restart-strategy 定义了哪种策略会被采用。 如果 checkpoint 未启动, 就会采用 no restart策略, 如果启动了 checkpoint 机制,但是未指定重启策略的话, 就会采用== fixed-delay== 策略,重试 Integer.MAX_VALUE 次。请参考下面的可用重启策略来了解哪些值是支持的。
每个重启策略都有自己的参数来控制它的行为, 这些值也可以在配置文件中设置,每个重启策略的描述都包含着各自的配置值信息。
在这里插入图片描述
除了定义一个默认的重启策略之外, 你还可以为每一个 Job 指定它自己的重启策略, 这个重启策略可以在 ExecutionEnvironment 中调用 setRestartStrategy()方法来程序化地调用,主意这种方式同样适用于 StreamExecutionEnvironment。

下面的例子展示了我们如何为我们的 Job 设置一个固定延迟重启策略, 一旦有失败, 系统就会尝试每 10 秒重启一次, 重启 3 次。

val env = ExecutionEnvironment.getExecutionEnvironment() 
// 重启次数  // 延迟时间间隔 
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, Time.of(10, TimeUnit.SECONDS)))

固定延迟重启策略(Fixed Delay Restart Strategy)

固定延迟重启策略会尝试一个给定的次数来重启 Job, 如果超过了最大的重启次数,
Job 最终将失败。 在连续的两次重启尝试之间, 重启策略会等待一个固定的时间。
重启策略可以配置  flink-conf.yaml 的下面配置参数来启用,作为默认的重启策略:
restart-strategy: fixed-delay
第一种:全局配置 flink-conf.yaml
restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 3
restart-strategy.fixed-delay.delay: 10 s
第二种: 应用代码设置
val env = ExecutionEnvironment.getExecutionEnvironment()
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, // 重启次数
Time.of(10, TimeUnit.SECONDS) // 重启时间间隔
))

失败率重启策略

失败率重启策略在 Job 失败后会重启, 但是超过失败率后, Job 会最终被认定失败。
在两个连续的重启尝 试之间, 重启策略会等待一个固定的时间。
失败率重启策略可以在 flink-conf.yaml 中设置下面的配置参数来启用:
restart-strategy:failure-rate
第一种: 全局配置 flink-conf.yaml
restart-strategy: failure-rate
restart-strategy.failure-rate.max-failures-per-interval: 3
restart-strategy.failure-rate.failure-rate-interval: 5 min
restart-strategy.failure-rate.delay: 10 s
第二种: 应用代码设置
val env = ExecutionEnvironment.getExecutionEnvironment()
env.setRestartStrategy(RestartStrategies.failureRateRestart(3, // 每个测量时间间隔最大失败次数
Time.of(5, TimeUnit.MINUTES), //失败率测量的时间间隔 Time.of(10, TimeUnit.SECONDS) // 两次连续重启尝试的时间间隔
))

无重启策略

Job 直接失败,不会尝试进行重启
第一种: 全局配置 flink-conf.yaml
restart-strategy: none
第二种: 应用代码设置
ExecutionEnvironment env	=ExecutionEnvironment.getExecutionEnvironment(); 
env.setRestartStrategy(RestartStrategies.noRestart());

Checkpoint 保存快照重启策略

package com.czxy.flink.stream.checkpoint
import org.apache.flink.api.common.restartstrategy.RestartStrategies
import org.apache.flink.runtime.state.filesystem.FsStateBackend 
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.scala.{DataStream,StreamExecutionEnvironment}

/**
*单词统计测试checkpoint
*执行步骤:
*1.创建执行环境
*2.开启checkpoint,并设置周期时间,每5秒钟做一次checkpoint
*3.制定快照保存的方式,默认是内存,建议不使用,这里设置为磁盘文件的方式或者是hdfs中
*4.如果程序被 cancel,保留以前做的 checkpoint
*5.程序出现异常是会重启,重启五次,每次延迟 5 秒,如果超过了 5 次, 程序退出
*6.构建数据源
*7.数据处理
*8.打印输出
*9.执行任务
*/
object StreamCheckpointDemo {
def main(args: Array[String]): Unit = { //1.创建执行环境
val env: StreamExecutionEnvironment =
StreamExecutionEnvironment.getExecutionEnvironment
//2.开启checkpoint,并设置周期时间,每5秒钟做一次checkpoint
env.enableCheckpointing(5000)
//3.制定快照保存的方式,默认是内存,建议不使用
//这里设置为磁盘文件的方式或者是hdfs中
env.setStateBackend(new FsStateBackend(args(0)))
//如果程序被 cancel,保留以前做的 checkpoint
env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedChe ckpointCleanup.RETAIN_ON_CANCELLATION)
//程序出现异常是会重启, 重启五次, 每次延迟 5 秒, 如果超过了 5 次, 程序退出
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(5, 5000))
//4.构建数据源
val source: DataStream[String] = env.socketTextStream(args(1), args(2).toInt)
//导入隐式转换包
import org.apache.flink.api.scala._
//5.数据处理
val result = source.flatMap(x => x.split(" ")).map(word => { if (word.equals("laowang")) {
} throw new RuntimeException("老王来了,重启程序!")
} (word, 1)
).keyBy(0).sum(1)
//6.打印输出
result.print()
//7.执行任务
env.execute(this.getClass.getSimpleName)
} 

Flink 的状态管理

我们前面写的 wordcount 的例子,没有包含状态管理。如果一个 task 在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需要重新计算。
从容错和消息处理的语义上(atleast once, exactly once),Flink 引入了 state 和 checkpoint
【因此可以说 flink 因为引入了 state 和 checkpoint 所以才支持的 exactly once】首先区分一下两个概念
state
state 一般指一个具体的 task/operator 的状态
【state 数据默认保存在 java 的堆内存中,TaskManage 节点的内存中】【operator 表示一些算子在运行的过程中会产生的一些中间结果】 checkpoint
checkpoint【可以理解为 checkpoint 是把 state 数据定时持久化存储了】,则表示了一个 Flink Job 在一个特定时刻的一份全局状态快照,即包含了所有 task/operator 的状态注意:task(subTask)是 Flink 中执行的基本单位。operator 指算(transformation)。 State 可以被记录,在失败的情况下数据还可以恢复。
Flink 中有两种基本类型的 State

  • Keyed State

  • Operator State

  • 原始状态(raw state)

  • 托管状态(managed state)

托管状态是由 Flink 框架管理的状态

【我们说 operator 算子保存了数据的中间结果,中间结果保存在什么类型中,如果我们这里是托管状态,则由 flink 框架自行管理】

原始状态由用户自行管理状态具体的数据结构,框架在做 checkpoint 的时候,使用 byte[] 来读写状态内容,对其内部数据结构一无所知。

通常在 DataStream 上的状态推荐使用托管的状态,当实现一个用户自定义的 operator 时,会使用到原始状态。

State-Keyed State

基于 KeyedStream 上的状态。这个状态是跟特定的 key 绑定的,对 KeyedStream 流上的每一个 key,都对应一个 state, 比如:stream.keyBy(…)
KeyBy 之后的 Operator State,可以理解为分区过的 Operator State
每 个 并 行 keyed Operator 的 每 个 实 例 的 每 个 key 都 有 一 个 Keyed State ,
即<parallel-operator-instance,key>就是一个唯一的状态 ,由于每个 key 属于一个 keyed Operator 的并行实例,因此我们将其简单的理解为<operator,key> 保存 state 的
数据结构
ValueState:即类型为 T 的单值状态。这个状态与对应的 key 绑定,是最简单的状态了。它可以通过 update 方法更新状态值,通过 value()方法获取状态值
ListState:即 key 上的状态值为一个列表。可以通过 add 方法往列表中附加值;也可以通过 get()方法返回一个 Iterable来遍历状态值ReducingState:这种状态通过用户传入的 reduceFunction,每次调用 add 方法添加值的时候,会调用 reduceFunction,最后合并到一个单一的状态值
MapState<UK, UV>:即状态值为一个 map。用户通过 put 或 putAll 方法添加元素
需要注意的是,以上所述的 State 对象,仅仅用于与状态进行交互(更新、删除、清空等),而真正的状态值,有可能是存在内存、磁盘、或者其他分布式存储系统中。相当于我们只是持有了这个状态的句柄。

State-Operator State

与Key 无关的 State,与 Operator 绑定的 state,整个 operator 只对应一个 state 保存 state 的数据结构 ListState
举例来说,Flink 中的 Kafka Connector,就使用了 operator state。它会在每个 connector 实例中,保存该实例中消费 topic 的所有(partition, offset)映射

Broadcast State

Broadcast State 是 Flink 1.5 引入的新特性。广播状态可以用来解决如下问题:一条流需要根据规则或配置处理数据,而规则或配置又是随时变化的。此时,就可将规则或配置作为广播流广播出去,并以 Broadcast State 的形式存储在下游 Task 中。下游 Task 根据 Broadcast State 中的规则或配置来处理常规流中的数据。

猜你喜欢

转载自blog.csdn.net/weixin_43563705/article/details/107614714
今日推荐