窗口及水印概念

一、窗口

窗口是一种机制。允许许多事件按照时间或者其他特征进行分组,将每一组作为整体去分析计算。 Window就是用来对一个无限的流设置一个有限的集合,在有界的数据集上进行操作的一种机制。window又可以分为基于时间(Time-based)的window以及基于数量(Count-based)的window。
1.1 flink 的 window 实现机制
Flink 中定义一个窗口主要需要以下三个组件。
  • Window Assigner:用来决定某个元素被分配到哪个/哪些窗口中去。
  • Trigger:触发器。决定了一个窗口何时能够被计算或清除,每个窗口都会拥有一个自己的Trigger。
  • Evictor:可以译为“驱逐者”。在Trigger触发之后,在窗口被处理之前,Evictor(如果有Evictor的话)会用来剔除窗口中不需要的元素,相当于一个filter。
1.2 window 分类
Flink 支持两种划分窗口的方式,按照time和count。如果根据时间划分窗口,那么它就是一个time-window ;如果根据数据划分窗口,那么它就是一个count-window。flink中的窗口主要分为3大类共5种窗口:
(1)时间窗口
时间窗口是最简单,最有用的一种窗口,它支持滚动和滑动,几个简单的例子,对传感器的发出的数据进行求和。
  • Tumbing Time Window 滚动时间窗口
一分钟滚动窗口收集最近一分钟的数值,并在一分钟结束时输出总和,如下图:
  • Sliding Time Window 滑动时间窗口
一分钟滑动窗口计算最近一分钟的数值总和,但是每半分钟滑动一次并输出结果,如下图:
第一个滑动窗口对 3,2,5,7求和得到17,半分钟后窗口滑动,然后对2,5,7,1求和得到结果15以此类推。
例如实现每过xxx时间 统计 xxx时间窗口的效果. 比如,我们可以每30秒计算一次最近一分钟用户购买的商品总数。可以使用滑动时间窗口。
时间类型
Flink 中的时间和其他流式计算系统的时间一样分为三类:事件时间 TimeCharacteristic.EventTime ,摄入时间 TimeCharacteristic.IngestionTime,处理时间 TimeCharacteristic.ProcessingTime 三种。
  • 事件生成时间:是每个独立事件在产生它的设备上发生的时间。
  • 接入时间:数据进入Flink系统的时间,接入时间依赖于Source Operator 所在主机系统的时间。
  • 处理时间:数据在操作算子计算过程中获取到的所在主机的时间。
如果以 EventTime 为基准来定义时间窗口将形成EventTimeWindow,要求消息本身就应该携带EventTime。
如果以 IngesingtTime 为基准来定义时间窗口将形成 IngestingTimeWindow,以 source 的systemTime为准。
如果以 ProcessingTime 基准来定义时间窗口将形成 ProcessingTimeWindow,以 operator 的systemTime 为准。
(2)计数窗口
计数窗口的分组依据不再是时间,而是元素的数量。
  • Tumbing Count Window 滚动计数窗口
当我们想要每100个用户购买行为事件统计购买总数,那么每当窗口中填满100个元素了,就会对窗口进行计算,这种窗口我们称之为翻滚计数窗口(Tumbling Count Window)
  • Sliding Count Window 滑动计数窗口
和Sliding Time Window含义是类似的,例如计算每10个元素计算一次最近100个元素的总和
注意:计数窗口不如时间窗口那么严谨,要谨慎使用,比如其定义的元素数量为100,然而某一个key对应的元素永远达不到100个,那么计数窗口就会永远不关闭,则被该窗口占用的内存就浪费了,一种解决办法就是用时间窗口触发超时。
(3)会话窗口
会话指的是活动阶段,其前后都是非活动阶段,例如某用户在与网站进行一系列的交互之后,关闭浏览器或者不在交互(非活动阶段)。会话需要有自己的处理机制,因为他们通常没有固定的持续时间,或者说固定的交互次数(有的可能点击3次就购买了物品,有的可能点击40次才购买物品)。
在flink中。会话窗口由时间设定。既希望等待多久认为会话已经结束。

二、Flink 水印

现在有一个问题就是:如何判断所有的事件是否都已经到达,以及何时计算和输出窗口的结果?换言之就是:如何追踪事件时间,并知晓输入数据已经流入到某个事件时间呢?为了追踪事件时间,需要依靠由数据驱动的时钟,而不是系统时间。
Watermark 是 Apache Flink 为了处理 EventTime 窗口计算提出的一种机制, 本质上是一种时间戳。一般来讲Watermark经常和Window一起被用来处理乱序事件。 Flink水印的本质是DataStream中的一种特殊元素,每个水印都携带有一个时间戳。当时间戳为T的水印出现时,表示事件时间t <= T的数据都已经到达,即水印后面应该只能流入事件时间t > T的数据。也就是说,水印是Flink判断迟到数据的标准,同时也是窗口触发的标记。
 
Flink 中的 Watermark 产生
上游:生成Watermark
  • AssignWithPeriodWatermarks:每隔N秒自动向流里注入一个Watermark,时间间隔由ExecutionConfig.setAutoWatermarkInterval()决定,每次调用getCurrentWatermark方法,如果得到的Watermark不为空,并且比之前的大就注入流中。
  • AssignWithPunctuatedWatermarks:基于事件向流里注入一个Watermark,每一个元素都有机会判断是否生成一个Watermark,如果得到的Watermark不为空且比之前的大就注入流中。
下游:处理Watermark
  • StatusWatermarkValve 负责将不同Channel 的Watermark 对齐,再传给pipeline 下游,对齐的概念是当前Channel的Watermark时间大于所有Channel。
 
Flink 中的 Watermark 传输及计算机制
  • Watermark传输方式:广播
这里的广播是指 上游算子的一个并发会往能够连接到的下游算子的所有并发广播,这与上下游算子并发之间的 Shuffle 机制有关。这里的广播不是说 Flink 提供的BroadCast 编程 API。
举例:如果一个任务 100 并发,上下游算子之间 Shuffle 策略是 Forward,那么上游算子的一个并发的 Watermark 会只往下游算子的连接到的那一个并发发送 Watermark;
如果策略是 Hash\Rebalance,则上游算子的一个并发的 Watermark 会往下游算子的所有并发上发送 Watermark。
  • Watermark 计算方式
       下游算子接受到上游算子并发的 Watermark 之后,下游算子当前并发的 Watermark 计算方式(这里的上下游是指有 Channel 连接的),计算公式:
       下游算子并发 Watermark = min(上游算子并发 1 发送的 Watermark,上游算子并发 2 发送的 Watermark......)。
       即下游算子并发 Watermark = 所有上游算子并发发到下游算子 Watermark 的最小值。
  • Watermark 对齐:下游算子并发的 Watermark 依赖上游算子并发的 Watermark 差异很大时,这就是 Watermark 没有对齐,举例:上有算子一个并发传输的 Watermark 是 23:59 分,另一个并发传输的 Watermark 是 23:00 分,中间查了 59 分钟,这种情况一般都是异常情况,所以叫做没有对齐。反之如果 Watermark 差异很小,则叫做Watermark 对齐。
 
为了形象地说明水印的作用,参考一下下面的图,是一个乱序的基于事件时间的数据流示例。
 
图中的方框就是数据元素,其中的数字表示事件时间,W(x)就表示时间戳是x的水印,并有长度为4个时间单位的滚动窗口。假设时间单位为秒,可见事件时间为2、3、1s的元素都会进入区间为[1s, 4s]的窗口,而事件时间为7s的元素会进入区间为[5s, 8s]的窗口。当水印W(4)到达时,表示已经没有t <= 4s的元素了,[1s, 4s]窗口会被触发并计算。同理,水印W(9)到达时,[5s, 8s]窗口会被触发并计算,以此类推。
不过图中暂时没有示出迟到数据。如果事件时间为6的元素出现在W(9)后面,就算是迟到了。
上面的示例只有一个并行度,那么在有多个并行度的情况下,就会有多个流产生水印,窗口触发时该采用哪个水印呢?答案是所有流入水印中时间戳最小的那个。来自官方文档的图能够说明问题。
多并行度下的WaterMark
一个window可能会接受到多个waterMark,我们以最小的为准。容易理解,如果所有流入水印中时间戳最小的那个都已经达到或超过了窗口的结束时间,那么所有流的数据肯定已经全部收齐,就可以安全地触发窗口计算了。
 //把并行度设置为2 
env.setParallelism(2);
 
输入数据:
000001,1461756870000 
000001,1461756883000 
000001,1461756888000
 
输出结果:
当前线程ID:55event = (000001,1461756883000)|19:34:43|19:34:43|19:34:33 
 
当前线程ID:56event = (000001,1461756870000)|19:34:30|19:34:30|19:34:20 
当前线程ID:56event = (000001,1461756888000)|19:34:48|19:34:48|19:34:38 
处理时间:19:31:25 
window start time : 19:34:30 
2> [(000001,1461756870000)|19:34:30] 
window end time : 19:34:33
ID为56的线程有两个WaterMark:20,38,那么38这个会替代20,所以ID为56的线程的WaterMark是38。然后ID为55的线程的WaterMark是33,而ID为56的WaterMark是38,会在里面求一个小的值作为waterMark,就是33,这个时候会触发Window为[30-33)的窗口,那这个窗口里面就有(000001,1461756870000)这条数据。
 
Flink对迟到数据的处理
水位线可以用来平衡计算的完整性和延迟性两方面。迟到的元素是指当这个元素来到时,这个元素所对应的窗口已经计算完毕了(也就是说水位线已经没过窗口结束时间了)。这说明迟到这个特性只针对事件时间。
DataStream API提供了三种策略来处理迟到元素
(1)直接抛弃迟到的元素
抛弃迟到的元素是event time window operator的默认行为。也就是说一个迟到的元素不会创建一个新的窗口。process function可以通过比较迟到元素的时间戳和当前水位线的大小来很轻易的过滤掉迟到元素。
(2)将迟到的元素发送到另一条流中去
迟到的元素也可以使用侧输出(side output)特性被重定向到另外的一条流中去。迟到元素所组成的侧输出流可以继续处理或者sink到持久化设施中去。
(3)可以更新窗口已经计算完的结果,并发出计算结果。
posted @ 2022-12-28 14:21  司也  阅读(244)  评论(0)    收藏  举报