- 窗口:
在上一章中已经了解了 Flink 中事件时间和水位线的概念,那它们有什么具体应用呢?当然是做基于时间的处理计算了。其中最常见的场景,就是窗口聚合计算。 之前我们已经了解了 Flink 中基本的聚合操作。在流处理中往往面对的是连续不断、无休无止的无界流,不可能等到所有所有数据都到齐了才开始处理。所以聚合计算其实 只能针对当前已有的数据——之后再有数据到来,就需要继续累加、再次输出结果。这样似乎 很“实时”,但现实中大量数据一般会同时到来,需要并行处理,这样频繁地更新结果就会给 系统带来很大负担了。 更加高效的做法是,把无界流进行切分,每一段数据分别进行聚合,结果只输出一次。这 就相当于将无界流的聚合转化为了有界数据集的聚合,这就是所谓的“窗口”(Window )聚合 操作。窗口聚合其实是对实时性和处理效率的一个权衡。在实际应用中,我们往往更关心一段 时间内数据的统计结果,比如在过去的一 分钟内有多少用户点击了网页。在这种情况下,我们 就可以定义一个窗口,收集最近一分钟内的所有用户点击数据,然后进行聚合统计,最终输出 一个结果就可以了。 在 Flink 中,提供了非常丰富的窗口操作,下面我们就来详细介绍。
-
窗口的概念:
Flink 是一种流式计算引擎,主要是来处理无界数据流。想 要更加方便高效地处理无界流,一种方式就是将无限数据切割成有限的“数据块”进行处理,这 就是所谓的“窗口”(Window)。 在 Flink 中, 窗口就是用来处理无界流的核心。我们很容易把窗口想象成一个固定位置的 “框”,数据源源不断地流过来,到某个时间点窗口该关闭了,就停止收集数据、触发计算并输 出结果。例如,我们定义一个时间窗口,每 10 秒统计一次数据,那么就相当于把窗口放在那 里,从 0 秒开始收集数据;到 10 秒时,处理当前窗口内所有数据,输出一个结果,然后清空 窗口继续收集数据;到 20 秒时,再对窗口内所有数据进行计算处理,输出结果;依次类推, 如图所示。这里注意为了明确数据划分到哪一个窗口,用数学符号表示就是一个左闭右开的区间,例如 0~10 秒的窗口可以表示为[0, 10),这里单 位为秒。对于处理时间下的窗口而言,这样理解似乎没什么问题。因为窗口的关闭是基于系统时间的,当前窗口关闭就只能去下一个窗口正如上图中,0~10 秒的窗口关闭后,可能还有时间戳为 9 的数据会来,它就只能进入 10~20 秒的窗口了。这样会造成窗口处理结果的不准确。所以我们需要设置一个延迟时间来等所有数据到齐。比如上面的例子中,我们可以设置延迟时间为 2 秒,这样 0~10 秒的窗口会在时间戳为 12 的数据到来之后,才真正关闭计算输出结果,这 样就可以正常包含迟到的 9 秒数据了如图所示。
但是这样一来, 0~10 秒的窗口不光包含了迟到的 9 秒数据,连 11 秒和 12 秒的数据也包含进去了。我们为了正确处理迟到数据,结果把早到的数据划分到了错误的窗口——最终结果都是错误的。 所以相比之下,我们应该把窗口理解成一个“桶” 。在 Flink 中,窗口可以把流切割成有限大小的多个“存储桶”(bucket) ;每个数据都会分发到对应的桶中,当到达窗口结束时间时,就对每个桶中收集的数据进行计算处理如图所示。我们可以梳理一下事件时间语义下,之前例子中窗口的处理过程:( 1 )第一个数据时间戳为 2 ,判断之后创建第一个窗口 [0, 10 ),并将 2 秒数据保存进去;(2)后续数据依次到来,时间戳均在 [0, 10 )范围内,所以全部保存进第一个窗口;(3) 11 秒数据到来,判断它不属于 [0, 10 )窗口,所以创建第二个窗口 [10, 20 ),并将 11秒的数据保存进去。由于水位线设置延迟时间为 2 秒,所以现在的时钟是 9 秒,第一个窗口也没有到关闭时间;(4)之后又有 9 秒数据到来,同样进入 [0, 10 )窗口中;(5) 12 秒数据到来,判断属于 [10, 20 )窗口,保存进去。这时产生的水位线推进到了 10秒,所以 [0, 10 )窗口应该关闭了。第一个窗口收集到了所有的 7 个数据,进行处理计算后输出结果,并将窗口关闭销毁;(6)同样的,之后的数据依次进入第二个窗口,遇到 20 秒的数据时会创建第三个窗口 [20,30)并将数据保存进去;遇到 22 秒数据时,水位线达到了 20 秒,第二个窗口触发计算,输出结果并关闭。这里需要注意的是, Flink 中窗口并不是静态准备好的,而是动态创建——当有落在这个窗口区间范围的数据达到时,才创建对应的窗口。另外,这里我们认为到达窗口结束时间时,窗口就触发计算并关闭,事实上“触发计算”和“窗口关闭”两个行为也可以分开,这部分内容我们会在后面详述。
- 窗口的分类:
在 Flink 中,窗口的应用非常灵活,我们可以使用各种不同类型的窗口来实现需求。接下来我们就从不同的角度,对Flink中内置的窗口做一个分类说明。
- 按照驱动类型分类:
窗口本身是截取有界数据的一种方式。换句话说,就是以什么方式来开始和结束数据的截取,我们把它叫作窗口的“驱动类 型”。 我们最容易想到的就是按照时间段去截取数据,这种窗口就叫作“时间窗口”(Time Window)。之前所举的例子也都是时间窗口。除了由时间驱动之外, 窗口其实也可以由数据驱动,也就是说按照固定的数量,来截取一段数据集,这种窗口叫作“计数窗口”(Count Window),如图所示。1)时间窗口(Time Window):
时间窗口以时间点来定义窗口的开始时间 和结束时间,截取出的就是某一时间段的数据。到结束时间时, 窗口不再收集数据, 触发计算输出结果, 并将窗口关闭销毁。
用结束时间减去开始时间,得到这段时间的长度, 就是窗口的大小(window size)。这里的时间可以是不同的语义,既可以是处理时间窗口也可以是事件时间窗口。
Flink 中有一个专门的类来表示时间窗口, 名称就叫作 TimeWindow。这个类只有两个私 有属性:start 和 end ,表示窗口的开始和结束的时间戳,单位为毫秒。另外,TimeWindow 还提供了一个 maxTimestamp()方法,用来获取窗口中能够包含数据的最大时间戳
2)计数窗口(Count Window):
计数窗口基于元素的个数来截取数据,到达固定的个数时就触发计算并关闭窗口。每个窗口截取数据的个数,就是窗口的大小。
计数窗口相比时间窗口就更加简单,只需指定窗口大小,就可以把数据分配到对应的窗口中了。在 Flink 内部也并没有对应的类来表示计数窗口,底层是通过“全局窗口”(Global Window)来实现的。 - 按照窗口分配数据的规则分类:
时间窗口和计数窗口,只是对窗口的一个大致划分;在具体应用时,还需要定义更加精细的规则,来控制数据应该划分到哪个窗口中去。不同的分配方式,就可以有不同的功能。根据分配数据的规则,窗口的具体实现可以分为 4 类:滚动窗口( Tumbling Window )、 <
Flink窗口
于 2022-09-04 18:00:35 首次发布