4.时间与窗口

4.1 时间类型

在Flink中定义了3种时间类型:

  • 事件时间(Event Time):事件的发生事件,数据本身自带时间字段。
  • 处理时间(Processing Time):计算引擎处理时的系统时间。
  • 和摄取时间(Ingestion Time):指事件进入流处理系统的时间。

4.2 窗口类型

  • 计数窗口(Count Window):滚动/滑动
  • 时间窗口(Time Window):滚动/滑动
  • 会话窗口(Session Window):当超过一段时间,该窗口没有收到新的数据元素,则视为该窗口结束。

4.3 窗口原理与机制

  1. 数据流进入算子前,被提交给WindowAssigner,决定元素被放到哪个或哪些窗口,同时可能会创建新窗口或者合并旧的窗口。
  2. 每一个窗口都拥有一个属于自己的触发器Trigger,每当有元素被分配到该窗口,或者之前注册的定时器超时时,Trigger都会被调用。
  3. Trigger被触发后,窗口中的元素集合就会交给Evictor(如果指定了),遍历窗口中的元素列表,并决定最先进入窗口的多少个元素需要被移除。
  4. 窗口函数计算结果值,发送给下游。

PS:Flink对一些聚合类的窗口计算(如sum和min)做了优化,因为只需要保存一个中间结果值。每个进入窗口的元素都会执行一次聚合函数并修改中间结果值。

(1)WindowAssigner:决定某个元素被分配到哪个/哪些窗口中去。

(2)WindowTrigger:拥有定时器,决定窗口何时触发/清除。处理时间和计数窗口的实现基于触发器完成。(事件时间窗口触发:watermark ≥ 窗口endTime

(3)WindowEvictor:窗口数据的过滤器,可在WindowFunction执行前或后,从Window中过滤元素。

1)CountEvictor: 计数过滤器。在Window中保留指定数量的元素,并从窗口头部开始丢弃其余元素。 2)DeltaEvictor: 阈值过滤器。本质上来说就是一个自定义规则,计算窗口中每个数据记录,然后与一个事先定义好的阈值做比较,丢弃超过阈值的数据记录。 3)TimeEvictor: 时间过滤器。保留Window中最近一段时间内的元素,并丢弃其余元素。

(4)Window函数:

1)增量计算函数:数据到达后立即计算,窗口只保存中间结果。效率高,性能好,但不够灵活。

2)全量计算函数:缓存窗口的所有元素,触发后统一计算,效率低,但计算灵活。

4.4 水印

水印(Watermark)用于处理乱序事件。(也就是迟到数据)

4.4.1 watermark生成

  1. Datastream watermark生成
    1. Source function中生成
    2. DataStream API中生成
      1. AssignerWithPeriodicWatermarks:系统周期性的调用getCurrentWatermark()来获取当前的Watermark,它返回的Watermark仅在大于上一次返回的Watermark情况下有效
        1. BoundedOutOfOrdernessTimestampExtractor:初始化Watermark = Long.MIN_VALUE,对每条数据,根据extractTimestamp获取最大时间戳currentMaxTimestamp。周期性的调用getCurrentWatermark获取当前最新的Watermark。Watermark=当前收到的数据元素的最大 时间戳-固定延迟
        2. AscendingTimestampExtractor:默认是顺序数据,Watermark=当前收到的数据元素的时间戳-1。减1的目的是确保有最大时间戳的事件不会被当做迟到数据丢弃(书上说 -1 是为了确保有最大时间戳的事件不会被当做迟到数据丢弃,私认为不对,窗口是左闭右开的,最大时间戳的事件会被分配给下一个窗口,此时上一个窗口触发,不代表会丢弃这条数据,因为是在下一个窗口触发时计算
        3. IngestionTimeExtractor:周期性调用getCurrentWatermark() 获取当前机器时间作为当前的Watermark。
      2. AssignerWithPunctuatedWatermarks:对每一条数据生成一个watermark,它返回的Watermark仅在大于上一次返回的Watermark情况下有效
  2. Flink SQL:与Datastream类似,主要是在TableSource中完成。

在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark,会对下游算子造成一定计算压力,适用于实时性要求较高和TPS低的场景。

周期性适用于实时性要求不高,和TPS高的场景。

4.4.2 多流watermark

Apache Flink 内部要保证Watermark保持单调递增。存在多source watermark不一致的问题。

经过keyBy、partition之后,在Flink的底层执行模型上,多流输入会被分解为多个双流输入。下游watermark 取所有上游的最小值

4.5 时间服务

4.5.1 定时器服务

定时器服务在Flink中叫作TimerService,TimeService是在算子中提供定时器的管理行为, 包含定时器的注册和删除。在算子中使用时间服务来创建定时器(Timer),并且 在Timer触发的时候进行回调,从而进行业务逻辑处理。

4.5.2 定时器

定时器在Flink中叫作Timer。注册Timer然后重写其onTimer()方法,在Watermark超过Timer的时间点之后,触发回调onTimer()。

  • 对于事件时间,会根据Watermark,从事件时间的定时器队列中找到比给定时间小的所有定时器,触发该Timer所在的算子,然后由算子去调用UDF中的onTime()方法
  • 处理时间是从处理时间 Timer优先级队列中找到Timer。处理时间因为依赖于当前系统,所以其使用的是周期性调度。

4.5.3 优先队列

Flink自己实现了优先级队列来管理Timer,共有2种实现。

  • 基于堆内存的优先级队列HeapPriorityQueueSet:基于Java堆 内存的优先级队列,其实现思路与Java的PriorityQueue类似,使用了二叉树。
  • 基于RocksDB的优先级队列:分为Cache+RocksDB量级,Cache 中保存了前N个元素,其余的保存在RocksDB中。写入的时候采用 Write-through策略,即写入Cache的同时要更新RocksDB中的数据,可 能需要访问磁盘。

基于堆内存的优先级队列比基于RocksDB的优先级队列性能好,但 是受限于内存大小,无法容纳太多的数据;基于RocksDB的优先级队列 牺牲了部分性能,可以容纳大量的数据。

4.6 窗口实现

在 Flink 中 有 3 类 窗 口 : CountWindow 、 TimeWindow 、 SessionWindow,其执行时的算子是WindowOperator。

事件窗口用的比较少。在Flink中提供了4种Session Window的默认实现。

  1. ProcessingTimeSessionWindows:处理时间会话窗口,使用固定会话间隔时长。
  2. DynamicProcessingTimeSessionWindows : 处理时间会话窗口,使用自定义会话间隔时长。
  3. EventTimeSessionWindows:事件时间会话窗口,使用固定会话间隔时长。
  4. DynamicEventTimeSessionWindows:事件时间会话窗口,使用自定义会话间隔时长。

对于会话窗口,因为无法事先确定窗口的长度,也不知道该将数据元素放到哪个窗口,所以对于每一个事件分配一个SessionWindow。然后判断窗口是否需要与已有的窗口进行合并。窗口合并时按照窗口的起始时间进行排序,然后判断窗口之间是否存在时间重叠,重叠的窗口进行合并。

猜你喜欢

转载自blog.csdn.net/qq_35590459/article/details/132177154