Flink 窗口函数

Flink 窗口机制

窗口概述:

  • 窗口是Flink用来处理无界流的核心,窗口将流切成有界的桶,之后就可以在bucket基础上对数据计算。所以窗口的单位是桶。

为什么要使用窗口?

  • 流式处理中数据都是源源不断的来,不可能等到所有数据都到了之后才开始计算,而我们可以定义一个时间段这个间隔内的数据进行计算,这个时间阶段就是窗口,窗口是一种切割无限数据为有限块进行处理的手段。

窗口分类:

  • 基于事件的窗口> 事件窗口就是基于事件数据个数的窗口,是Flink特有的,与时间无关。

    1. 滚动窗口:'countWindow(3)'只需指定窗口大小,当元素数量达到窗口大小就会触发计算。

    2. 滑动窗口:'countWindow(3,2)'滑动窗口与滚动窗口同名只需多传个参数,作为步长,每收到2个相同key的数据就会计算一次,最多3个。

      说明:分组前开窗用windowAll()是非并行化的算子,所有的数据都会进入一个并行实例。分组后开窗用window()每个组一个窗口,每个组单独输出自己的,分组计算。

  • 基于时间的窗口> 时间窗口就是基于处理时间、和事件时间驱动的窗口,时间窗口需要指定窗口分配器。

    1. 滚动窗口:滚动窗口有固定的大小,窗口与窗口之间不能重叠也不存在缝隙,每个事件只能属于一个窗口。

      说明:滚动窗口左闭右开,每个事件只能属于一个窗口。

    2. 滑动窗口:与滚动窗口一样, 滑动窗口也是有固定的长度,多了滑动步长,用来控制滑动窗口启动的频率,滑动步长小于窗口长度时窗口会重叠,每个数据都会被分配到多个窗口中。

      说明:滑动窗口每经过一个步长都会有一个窗口关闭输出,并且会向前多滑动一个窗口计算。
      数据的最大重复次数:长度除以步长为一个数据最多的重复计算次数。

    3. 会话窗口:有一段时间没有收到数据,会话窗口会自动关闭,后序的元素会被分配到一个新的会话窗口,这段没有收到数据的时间就是会话窗口的gap。

      说明:会话窗口不会有重叠,与滚动窗口和滑动窗口相比,会话窗口也没有固定的开启和关闭时间。

      原理:在Flink内部,每到达一个新的元素都会创建一个新的会话窗口,如果这些窗口彼此相距比较定义的间隔小, 则会对他们进行合并。为了能够合并会话窗口算子需要合并触发器和合并窗口函数。

    4. 4.全局窗口:全局窗口分配器会分配相同key的所有元素进入同一个全局窗口,该窗口只有指定自定义的触发器时才有用,否则不会触发计算。

    说明:
    1.无论那种窗口都是以事件为驱动的,只有事件来了才会触发计算,不来不计算。
    2.FLink窗口的划分并不是以第一条数据为基准的。

窗口函数:

  1. 增量聚合函数:来一个数据计算一次,窗口结束时输出一次。

    • ReduceFunction:Reduce方法是对两个数据进行聚合,第一个数据是上一次的结果,同一窗口内同一分组的数据来的时候不会进入reduce方法。
    • AggregateFunction:需要定义累加器并初始化,相比reduce更加灵活,输入类型、中间状态、输出结果都可以不同。
  2. 全窗口函数:每来一条数据就先存起来,到窗口触发计算时一起计算。

    • ProcessWindowFunction:内有一个收集器用于向下游发送数据,可以自定义触发器变成增量聚合。

    说明:由于全窗口函数需要将已经接收到的数据先存起来,等到窗口触发时机到了在一起执行计算,当数据量很大时所有数据都存储在内存中可能会导致内存泄漏OOM,此时就可以通过自定义触发器修改全窗口函数的触发时机,或者也可以使用窗口聚合函数,传一个增量聚合函数和一个全窗口函数。

时间语义:

  1. 处理时间:服务器处理的时间,容易存在数据漂移。

  2. 事件时间:事件发生的事件,事件时间不能用来表明时间的进度,可能存在数据乱序的问题,因此需要通过waterMark水印来表示事件的进展。

  3. 注入时间:事件刚刚进入Flink的系统事件就是事件的注入时间,即source数据时的时间。

    说明:Flink1.12 版本默认事件时间语义,之前版本默认为处理时间语义,凡是使用事件时间都需要指定Watermark以及时间字段的提取。

WaterMark:

  • 概念:
    1.衡量事件时间的进展。
    2.是单调不减的,要么增加要么保持不变。
    3.是特殊的数据,作为数据流的一部分随数据流传递,并且携带一个时间戳t。
    4.用来解决数据流的事件时间乱序问题。
    5.WaterMark认为在此之前的数据都已经处理过,在此之后的数据认为是迟到数据。
    6.窗口的计算和关闭的触发、定时器的触发都依赖 Watermark。

    说明:事件时间的机制即使事件是有序的必须使用watermark,因为所有的计算触发、窗口的关闭执行等都需要watermark。

  • Watermark 分类:

    间歇性的生成>来一条数据更新一次 => onEvent(),每来一条数据都生成一个watermark会导致性能降低。
    周期性的生成>默认的生成方式,间隔固定周期更新一次,默认200ms => onPeriodicEmit()。

  • Watermark 生成逻辑:

    1. 升序>Watermark = 当前最大事件时间 - 1ms
      WatermarkStrategy
      .forMonotonousTimestamps()
      .withTimestampAssigner()

    2. 乱序>Watermark = 当前最大事件时间 - 乱序程度 - 1ms
      WatermarkStrategy
      .forBoundedOutOfOrderness(Duration)
      .withTimestampAssigner()

      说明:

      1.有序时乱序程度为0,Watermark默认是Long的最小值。

      2.为了防止读文件太快而watermark没有更新还是Long的最小值太小了导致事件无法被触发,所以将watermark置为Long的最大值。

  • Watermark 多并行度传递:

    1.上游算子一个下游算子多个: 采用广播的形式。
    2.上游多个算子下游一个算子:以最小的watermark为准。

    说明:如果需要接收多个算子的Watermark,需要等待所有的Watermark都来了才可以计算,否则会一致等待。

  • 其他注意:
    1.如果上游是Kafka,直接在官方提供的SourceFunction实现类上,指定watermark,用来保证Kafka到Source之间的数据有序。
    2.多分区的数据源,设置一个idle超时,防止watermark不更新的问题。
    3.为了防止读文件太快而watermark没有更新还是Long的最小值太小了导致事件无法被触发,所以将watermark置为Long的最大值。

窗口允许迟到:

  • Flink的窗口允许迟到,用来解决关窗前的迟到数据,如果窗口设置了等待时间时,当Watermark大于等于窗口的最大值时只会触发计算而不会关闭,在允许迟到时间范围内每来一条数据都会触发一次计算,当Watermark大于等于窗口的最大值+等待时间时,窗口就会关闭,在此之后再来的数据窗口不处理而是放到测输出流中,使用测输出流来解决窗口关闭之后的迟到数据。

    说明:
    1.等待时间=最大乱序数(有序为0)。
    2.在窗口运行迟到的范围时间内,每来一条数据都会触发一次计算。

侧输出流:

  1. 测输出流用来接收窗口关闭之后的迟到数据,需要对窗口设置测输出流,并且窗口中的测输出流只能有一个参数为测输出流标签,如果需要汇总迟到数据需要使用Connect合并两条流,这仅仅是将两条流的数据放在一起进行存储,如果要聚合还要通过Process自定义实现。

  2. 使用测输出流可以将一个流分为多个流,使用测输出流可以在主流分析数据的同时在进行指标的监测,通过运行时上下文对象获取测输出流,并且该对象比窗口中的测输出流更灵活,可以有两个参数,第一个为输出流标签,第二个参数可以根据需求自定义。

    说明:测输出流必须以匿名内部类的方式在代码中指定否则会报错,侧输出流识别的是名字而不是标签,只要名字一样即便标签不一样也会被认为是同一个测输出流。

  3. 侧输出流还可以用来存储CEP中的超时和迟到数据。

触发器:

  • 作用就是决定何时触发窗口函数中的逻辑执行。

  • 三种模式:

    1. 'fire'>触发计算,默认。
    2. 'purge'>清除窗口中的元素。
    3. 'fire_and_purge'>触发计算并清空窗口中的元素。

    说明:读文件有界流时,可能窗口的处理时间远远大于读取文件的时间,窗口还没有触发计算文件就已经读取完了有界流程序就会退出,这种情况可以触发器修改窗口的触发时间。

定时器:

  • 基于事件时间或处理时间操作过每个数据后都注册一个定时器,然后在定时器指定时间触发并执行相应的操作。无论那种时间的定时器都可以通过运行时上下文对象注册或删除,待定时时间到会触发定时器执行回调函数,OnTimer方法用来触发定时器。

  • 定时器原理:

    1. 注册:每次调用注册方法,都会new一个定时器对象放到一个去重的队列中(仅对同分组内的定时器去重,不同分组的没关系),即使重复注册定时器也只会有一个定时器起作用。

    2. 触发:基于事件时间的触发器 WaterMark >= 定时器注册时间时会触发定时器。

      注意:WaterMark = 当前最大事件时间 - 1 毫秒 ,而定时器的注册时间就是个整数没有减1毫秒。

posted @ 2021-06-01 16:01  yuexiuping  阅读(938)  评论(0编辑  收藏  举报