Flink窗口(window)可以用于keyed streams和non-keyed streams
参考官方文档:https://nightlies.apache.org/flink/flink-docs-master/zh/docs/dev/datastream/operators/windows/
1.一些概念#
1.时间语义#
flink支持3种时间语义,分别是Event time,Processing time和Ingestion time。
参考:https://nightlies.apache.org/flink/flink-docs-release-1.17/docs/learn-flink/streaming_analytics/
1.Event time#
Event time的时间戳是每个单独事件在其产生设备上发生的时间。Event time通常在record进入Flink之前就嵌入到record中,这样就可以从每条record中提取事件时间戳。在Event time中,时间的进程取决于数据,而不是任何时钟。Event time程序必须指定如何生成Event time watermarks,这是一种以事件时间表示进度的机制。
在理想情况下,Event time处理将产生完全一致和确定的结果,而不管事件何时到达或它们的顺序如何。然而,除非事件是按顺序到达的(按时间戳),否则事件时间处理在等待乱序事件时会产生一些延迟。因为只能等待一段有限的时间,这就限制了Event time应用程序的确定性。
2.Processing time#
Processing time是指执行相应operator的机器的系统时间。
当流处理程序在Porcessing time上运行时,所有基于时间的操作(如时间窗口)将使用运行相应操作的机器的系统时钟。每小时的处理时间窗口将包括在系统时钟显示整个小时的时间之间到达特定操作员的所有记录。例如,如果应用程序在上午9点15分开始运行,第一个小时处理时间窗口将包括上午9点15分到10点之间处理的事件,下一个窗口将包括上午10点到11点之间处理的事件,以此类推。
Processing time是最简单的时间概念,不需要流和机器之间的协调。它提供了最好的性能和最低的延迟。然而,在分布式和异步环境中,处理时间不提供确定性,因为它容易受到记录到达系统中的速度(例如从消息队列)、记录在系统内部操作符之间流动的速度以及中断(计划或其他)的影响。
3.Ingestion time#
Flink在采集事件时记录的时间戳
2.watermarks水印#
watermarks是一种用于在event time中衡量进度的机制。watermarks作为data stream的一部门,带有一个时间戳t,watermark(t)表示在这个数据流中,event time已经到了t时刻,或者说在数据流中不会再有时间戳小于t时刻的元素
参考官方文档:Event Time and Watermarks
3.Lateness延迟#
某些元素可能会违反watermarks条件,也就是说,即使在watermark(t)发生之后,仍然会出现更多时间戳为t ‘ <= t的元素。事实上,在现实世界的许多设置中,某些元素可以任意延迟,因此不可能指定某个事件时间戳中所有元素的发生时间。此外,即使延迟时间是有限制的,但如果延迟太久,往往也会造成评估事件时间窗口的延迟。
因此,流程序可能会显式地期望一些迟到的元素。晚到元素是指在系统的事件时钟(由watermark表示)已经超过晚到元素的时间之后到达的元素。
允许延迟#
在使用 event-time 窗口时,数据可能会迟到,即 Flink 用来追踪 event-time 进展的 watermark 已经 越过了窗口结束的 timestamp 后,数据才到达。对于 Flink 如何处理 event time, event time 和 late elements 有更详细的探讨。
默认情况下,watermark 一旦越过窗口结束的 timestamp,迟到的数据就会被直接丢弃。 但是 Flink 允许指定窗口算子最大的 allowed lateness。 Allowed lateness 定义了一个元素可以在迟到多长时间的情况下不被丢弃,这个参数默认是 0。 在 watermark 超过窗口末端、到达窗口末端加上 allowed lateness 之前的这段时间内到达的元素, 依旧会被加入窗口。取决于窗口的 trigger,一个迟到但没有被丢弃的元素可能会再次触发窗口,比如 EventTimeTrigger
。
为了实现这个功能,Flink 会将窗口状态保存到 allowed lateness 超时才会将窗口及其状态删除 (如 Window Lifecycle 所述)。
默认情况下,allowed lateness 被设为 0
。即 watermark 之后到达的元素会被丢弃。
2.窗口window#
flink window有4个比较重要的组件:
- assigner(分配器):如何将元素分配给窗口
- function(计算函数):为窗口定义的计算:其实是一个计算函数,完成窗口内容的计算。
- triger(触发器):在什么条件下触发窗口的计算
- evictor(退出器):定义从窗口中移除数据
1.分配器(Assigner)#
flink内置了几种window assigner,比如滚动窗口(Tumbling Windows),滑动窗口(Sliding Windows),会话窗口(Session Windows),全局窗口(Global Windows)。
1.滚动窗口(Tumbling Windows)#
滚动窗口的 assigner 分发元素到指定大小的窗口。滚动窗口的大小是固定的,且各自范围之间不重叠。 比如说,如果你指定了滚动窗口的大小为 5 分钟,那么每 5 分钟就会有一个窗口被计算,且一个新的窗口被创建。
2.滑动窗口(Sliding Windows)#
与滚动窗口类似,滑动窗口的 assigner 分发元素到指定大小的窗口,窗口大小通过 window size 参数设置。 滑动窗口需要一个额外的滑动距离(window slide)参数来控制生成新窗口的频率。 因此,如果 slide 小于窗口大小,滑动窗口可以允许窗口重叠。这种情况下,一个元素可能会被分发到多个窗口。
比如说,你设置了大小为 10 分钟,滑动距离 5 分钟的窗口,你会在每 5 分钟得到一个新的窗口, 里面包含之前 10 分钟到达的数据。
3.会话窗口(Session Windows)#
会话窗口的 assigner 会把数据按活跃的会话分组。 与滚动窗口和滑动窗口不同,会话窗口不会相互重叠,且没有固定的开始或结束时间。 会话窗口在一段时间没有收到数据之后会关闭,即在一段不活跃的间隔之后。 会话窗口的 assigner 可以设置固定的会话间隔(session gap)或 用 session gap extractor 函数来动态地定义多长时间算作不活跃。 当超出了不活跃的时间段,当前的会话就会关闭,并且将接下来的数据分发到新的会话窗口。
4.全局窗口(Global Windows)#
全局窗口的 assigner 将拥有相同 key 的所有数据分发到一个全局窗口。 这样的窗口模式仅在你指定了自定义的 trigger 时有用。 否则,计算不会发生,因为全局窗口没有天然的终点去触发其中积累的数据。
2.窗口函数(Function)#
定义了 window assigner 之后,我们需要指定当窗口触发之后,我们如何计算每个窗口中的数据, 这就是 window function 的职责了。关于窗口如何触发,详见 triggers。
窗口函数有三种:ReduceFunction
、AggregateFunction
或 ProcessWindowFunction
。 前两者执行起来更高效(详见 State Size)因为 Flink 可以在每条数据到达窗口后 进行增量聚合(incrementally aggregate)。 而 ProcessWindowFunction
会得到能够遍历当前窗口内所有数据的 Iterable
,以及关于这个窗口的 meta-information。
使用 ProcessWindowFunction
的窗口转换操作没有其他两种函数高效,因为 Flink 在窗口触发前必须缓存里面的所有数据。 ProcessWindowFunction
可以与 ReduceFunction
或 AggregateFunction
合并来提高效率。 这样做既可以增量聚合窗口内的数据,又可以从 ProcessWindowFunction
接收窗口的 metadata。
1.ReduceFunction#
ReduceFunction
指定两条输入数据如何合并起来产生一条输出数据,输入和输出数据的类型必须相同。 Flink 使用 ReduceFunction
对窗口中的数据进行增量聚合。
2.AggregateFunction#
ReduceFunction
是 AggregateFunction
的特殊情况。 AggregateFunction
接收三个类型:输入数据的类型(IN
)、累加器的类型(ACC
)和输出数据的类型(OUT
)。 输入数据的类型是输入流的元素类型,AggregateFunction
接口有如下几个方法: 把每一条元素加进累加器、创建初始累加器、合并两个累加器、从累加器中提取输出(OUT
类型)。
与 ReduceFunction
相同,Flink 会在输入数据到达窗口时直接进行增量聚合。
3.ProcessWindowFunction#
ProcessWindowFunction 有能获取包含窗口内所有元素的 Iterable, 以及用来获取时间和状态信息的 Context 对象,比其他窗口函数更加灵活。 ProcessWindowFunction 的灵活性是以性能和资源消耗为代价的, 因为窗口中的数据无法被增量聚合,而需要在窗口触发前缓存所有数据。
3.触发器(Triggers)#
Trigger决定了一个窗口(由 window assigner 定义)何时可以被 window function 处理。 每个 WindowAssigner
都有一个默认的 Trigger
。 如果默认 trigger 无法满足你的需要,你可以在 trigger(...)
调用中指定自定义的 trigger。
1.Event-time window assigner 都默认使用 EventTimeTrigger。 这个 trigger 会在 watermark 越过窗口结束时间后直接触发。ProcessingTimeTrigger 根据 processing time 触发。
2.GlobalWindow 的默认 trigger 是永远不会触发的 NeverTrigger。因此,使用 GlobalWindow 时,你必须自己定义一个 trigger。
3.CountTrigger 在窗口中的元素超过预设的限制时触发。
4.PurgingTrigger 接收另一个 trigger 并将它转换成一个会清理数据的 trigger。
4.退出器(Evictors)#
Flink 的窗口模型允许在 WindowAssigner
和 Trigger
之外指定可选的 Evictor
。 如本文开篇的代码中所示,通过 evictor(...)
方法传入 Evictor
。 Evictor 可以在 trigger 触发后、调用窗口函数之前或之后从窗口中删除元素。