理解Flink中的流窗口

2020-07-07 00:00:00 元素 时间 窗口 触发器 计数

在这篇文章中,我们讨论了用于流处理的窗口的概念,介绍Flink的内置窗口,并解释它对自定义窗口语义的支持。

一、什么是窗口?窗口有什么用?

考虑一个交通传感器的例子,它每15秒计算一次通过某个位置的车辆数量。生成的流可能如下所示:

如果我们想知道,有多少车辆通过了该位置,可以简单地将单个计数相加。然而,传感器流的本质是不断地产生数据。这样的流永远不会结束,并且不可能计算出可以返回的终和。相反,可以计算滚动和,即,为每个输入事件返回一个更新的总和记录。这将产生一个新的部分和流。这将产生一个新流,其元素是一部分一部分的计数和。

然而,部分计数和的流可能不是我们想要的,因为它不断更新计数,甚至更重要的是,一些信息,如随时间的变化,会丢失。因此,我们可以重新表述我们的问题,即“每分钟经过该位置的汽车数量”。这要求我们将流的元素分组为有限集,每个集合对应60秒。此操作称为“翻窗操作(tumbling windows)”。

翻滚窗口将流离散化为不重叠的窗口。但对于某些应用程序,可能需要平滑的聚合。例如,我们可以“每三十秒计算后一分钟通过的汽车数量”。这样的窗口叫做“滑动窗口(sliding windows)”。

如前所述,在数据流上定义窗口是一种非并行操作。这是因为流的每个元素都必须由相同的窗口操作符处理,该操作符决定元素应该添加到哪个窗口。一个满流(full stream)中的窗口在Flink中称为"AllWindows"。对于许多应用程序,需要将数据流分组为多个逻辑流,每个逻辑流上可以应用一个窗口操作符。例如,考虑来自多个交通传感器的车辆计数流(而不是像我们前面的示例中那样只有一个传感器),其中每个传感器监视不同的位置。通过按传感器id分组流,我们可以并行计算每个位置的加窗流量统计。在Flink中,我们简单地将这种分区窗口称为"Windows",因为它们是分布式流的常见情况。下图显示了在一个(sensorId, count)对元素流上收集两个元素的翻转窗口。

一般来说,一个窗口定义了一个无界流上的有限元素集。这个集合可以基于时间(如我们前面的示例所示)、元素计数、计数和时间的组合,或者一些将元素分配到窗口的自定义逻辑。

Flink的DataStream API为常见的窗口操作提供了简洁的操作符,并提供了一种通用的窗口机制,允许用户定义完全定制的窗口逻辑。在下面,我们先介绍Flink的时间和窗口计数,然后再详细讨论它的窗口机制。

二、时间窗口

顾名思义,时间窗口按时间对流元素进行分组。例如,一个一分钟的翻滚时间窗口收集一分钟的元素,并在一分钟后对窗口中的所有元素应用一个函数。

在Apache Flink中定义翻滚和滑动时间窗口非常简单:

// (sensorId, carCnt)元素流
val vehicleCnts: DataStream[(Int, Int)] = ...

val tumblingCnts: DataStream[(Int, Int)] = vehicleCnts
  // 按传感器ID sensorId分组的keyd stream
  .keyBy(0) 
  // 1分钟长度的翻滚窗口
  .timeWindow(Time.minutes(1))
  // 在carCnt上求和
  .sum(1) 

val slidingCnts: DataStream[(Int, Int)] = vehicleCnts
  .keyBy(0) 
  // 滑动窗口,1分钟的时长,30秒的触发周期
  .timeWindow(Time.minutes(1), Time.seconds(30))
  .sum(1)

相关文章