Flink内核原理与实现
上QQ阅读APP看书,第一时间看更新

4.6 窗口实现

在Flink中窗口有两套实现,分别位于flink-streaming-java和flink-table-runtime-blink模块中,其会话窗口、时间窗口实现基本是一样的,计数窗口的实现不同。Flink-streaming-java中计数窗口依赖于GlobalWindow来实现,在flink-table-runtime-blink中,计数窗口与时间窗口一样有类定义和窗口分配器。

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

WindowOperator中数据处理的基本过程如图4-14所示。

图4-14 WindowOperator中数据处理过程

4.6.1 时间窗口

按照时间类型,窗口分为两类:处理时间窗口和事件时间窗口。

按照窗口行为,时间窗口分为两类:滚动窗口和滑动窗口。

滚动窗口(TumbleWindow)的关键属性有两个:

1)Offset:窗口的起始时间。

2)Size:窗口的长度。

滑动窗口(SlidingWindow)的关键属性有3个:

1)Offset:窗口的起始时间。

2)Size:窗口的长度。

3)Slide:滑动距离。

滚动窗口、滑动窗口其本质上是类似的,滚动窗口可以看作是滑动距离与窗口长度相同的滑动窗口。

在数据元素分配窗口的时候,对于滚动窗口,一个数据元素只属于一个窗口,但可能属于多个滑动窗口。

以最复杂的滑动窗口为例,如代码清单4-8所示。

代码清单4-8 基于事件时间的滑动窗口数据元素窗口分配示例

从上边的代码中可以看到,Flink会为每个数据元素分配一个或者多个TimeWindow对象,然后使用TimeWindow对象作为Key来操作窗口对应的State。

注:TimeWindow的equals方法,使用类型和窗口的起止时间进行相等比较,所以使用TimeWindow作为Key没有问题。

4.6.2 会话窗口

在需要登录的网站上,如果用户过一段时间没有操作,为了安全,账户就会自动退出。一般会通过会话超时设定一个时间,如30分钟。30分钟的超时间隔可以理解为会话窗口的Gap,如果从用户登录账户到超时退出的所有动作被看作事件的话,这些事件都处于同一个会话窗口中。

会话窗口的效果如图4-15所示。

在Flink中提供了4种Session Window的默认实现。

1)ProcessingTimeSessionWindows:处理时间会话窗口,使用固定会话间隔时长。

2)DynamicProcessingTimeSessionWindows:处理时间会话窗口,使用自定义会话间隔时长。

图4-15 会话窗口分割示例

3)EventTimeSessionWindows:事件时间会话窗口,使用固定会话间隔时长。

4)DynamicEventTimeSessionWindows:事件时间会话窗口,使用自定义会话间隔时长。

不同类型的会话窗口使用示例如代码清单4-9所示。

代码清单4-9 会话窗口使用示例

会话窗口不同于事件窗口,它的切分依赖于事件的行为,而不是时间序列,所以在很多情况下会因为事件乱序使得原本相互独立的窗口因为新事件的到来导致窗口重叠,而必须要进行窗口的合并,如图4-16所示。

图4-16 Session Window会话窗口合并示例

在图4-16中,元素8和元素7的时间间隔超过了会话窗口的超时间隔,所以生成了两个会话窗口。

元素4在会话窗口触发计算之前进入了Flink,此时因为元素4的存在,4与8的间隔、4与7的间隔都小于超时间隔,所以此时元素8、4、7应该位于一个会话窗口,那么此时就需要对窗口进行合并,窗口的合并涉及3个要素:

1)窗口对象合并和清理。

2)窗口State的合并和清理。

3)窗口触发器的合并和清理。

下面通过一个会话窗口的合并示例来理解其过程,如图4-17所示。

图4-17 会话窗口合并

(1)窗口合并

对于会话窗口,因为无法事先确定窗口的长度,也不知道该将数据元素放到哪个窗口,所以对于每一个事件分配一个SessionWindow。

然后判断窗口是否需要与已有的窗口进行合并。窗口合并时按照窗口的起始时间进行排序,然后判断窗口之间是否存在时间重叠,重叠的窗口进行合并,将后序窗口合并到前序窗口中,如图4-17所示,延长窗口W1的长度,将W3窗口的结束时间作为W1的结束时间,清理掉W2、W3窗口。

(2)State合并

窗口合并的同时,窗口对应的State也需要进行合并,默认复用最早的窗口的状态,本例中是W1窗口的状态,将其他待合并窗口的状态(W2、W3)合并到W1状态中。

创建状态需要跟StateBackend进行交互,成本比较高,对于会话窗口来说合并行为比较频繁,所以尽量复用已有的状态。

(3)触发器合并

Trigger#onMerge方法中用于对触发器进行合并,触发器的常见成本比较低,所以触发器的合并实际上是删除合并的窗口的触发器,本例中会删除W1、W2、W3的触发器,然后为新的W1窗口创建新的触发器,触发时间为W3触发器的触发时间。

4.6.3 计数窗口

在DataStream API中没有定义计数窗口的实体类,使用GlobalWindow来实现CoutntWindow。在DataStream API中使用CountWindow如代码清单4-10所示。

代码清单4-10 DataStream API中使用CountWindow

滚动计数窗口和滑动计数窗口依托于GlobalWindow实现,从实现上来说,对于一个Key,滚动计数窗口全局只有一个窗口对象,使用CountTrigger来实现窗口的触发,使用Evictor来实现窗口滑动和窗口数据的清理。

计数窗口与会话窗口类似,依赖于数据元素的行为,无法像时间窗口一样事先划分好窗口,其在处理过程中也会涉及窗口的合并。

使用Evictor的窗口,其最终运行在EvictorWindowOperator中,与普通的WindowOperator相比,EvictorWindowOperator多了一个对窗口State进行清理的动作。