Flink_窗口的底层实现逻辑

流处理窗口详解
本文深入探讨了Flink中的窗口概念,包括滚动窗口、滑动窗口、会话窗口及全局窗口等,并详细解析了窗口分配器WindowAssigner的工作原理及其不同实现。

目的

写这篇文目的是为了加深对窗口和 watermark 的理解。
先感谢这位博主的辛勤劳动。我做的分析就是基于这位大侠做的。

下面上正题。

正题

窗口的概念

窗口是用来切割无线流的,它把无线流切分成有限个碎片,通过计算碎片来计算流的某些性质。就像积分计算求球的体积。它将从球新到表面扇柱体是一个正方体,然后使用极限的思路,然后就计算出球体的体积。
在这里插入图片描述

从上图中,可以看到窗口的设计思路,也可以说成窗口的模型。

在这个模型里面,input stream 不停的发送数据给 WindowAssigner(窗口分配器) ,它负责将数据分配到个个窗口,根据分配的规则的不同,Flink 里面有下面几种窗口类型。

  1. 数据流是无限的,我们可以统计每 n 个单位时间内的一些统计值。这就是滚动窗口。
  2. 我们也可以每隔 30 秒,统计一下前1 分钟内的窗口中数据的一些统计统计值。着就是滑动窗口。如果将间隔时间设置为 0 ,则就变成了滚动窗口,所以可以将滚动窗口看作是滑动窗口的一种特殊情况。
  3. 上面是按照时间来指定数据的所在的窗口,也可以按照窗口的内数据的多少指派窗口,例如,窗口中的数据来到 10 个的时候,即计算窗口的数据。
  4. 对应用户行为日志的计算,需要判断用户使用停止操作,如果停止操作,则认为是一个 session 的结束,分析整个 session 内数据,这种场景对应的窗口就做 session window。
  5. 还有一种窗口是所有的数据都分配到一个窗口里面,这种叫做全局窗口。

Trigger 是干啥的呢?它的作用决定是否触发窗口的计算,触发的规则有很多,有预定义的,也可以我们自定义,一般是 eventtime 的场景下需要用心考虑如何设置此值。

Evictor 是抛弃者,当某个窗口被触发后,Evictor 负责按照规则将窗口中的数据抛弃,留下需要的,通常的情况下,Evictor 的实现是不会抛弃任何数据的。

Evaluation Function 是定义计算公式的,它接收窗口中的数据,然后按照预定义的公式计算出结果,例如,求和、求平均、求最大值,求最小值。

源码分析

弄清楚了 tumbling session count sliding 的功能,下面就从源码的角度弄清楚各窗口的 windown assigner、trigger 的不同。先来看 WindowAssigner 抽象类以及它下面的实现类。

先来理解一下 window assigner(WindowAssigner) 这个类。它负责将报文派发各自的窗口 。一条报文只能到一个窗口中去。

先来看看它有那些方法:

  1. assignerWindow( T , long ,WindowAssignerContent) : 返回一个集合,这个集合包含的元素是应该分发给某些窗口的。
  2. getDefualtTrigger():获取默认的 trigger 。

下面来具体的分析一下 WindowAssigner 的实现类。

TumblingEventTimeWindowAssigner 的窗口划分规则

第一个就是我们熟悉的 TumblingEventWindowAssigner ,然后关键的代码如下所示,

在这里插入图片描述
我们得到的结论:

  1. TumblingEventTimeWindowAssigner 是根据每个元素的 timestamp 来找到它对应的窗口。
  2. 找窗口的算法在 TimeWindow.getWindowStartWithoffset() 里面实现的。
  3. 算法是这样的,record 里面的 event time - ( eventtime - offset + 窗口大小 )%窗口的大小。
    我们将变量命名简化:
    record 里面的 event time :timestampt
    offset:偏移量。
    窗口大小:winSize
    所以上面的公式可以写成:timestamp - (timestamp - offset + winSize)%winSize。
    我们知道,timestamp 是 long 的整数,这个数是从计算机元年开始计算的一个正整数。
    我们可以将 timestamp 写成 n*winSize + m 这种写法,其中,n 是 timiestamp 里面有多少个
    winSize,m 是余数,是减掉 n 个 winSize 后,timestamp 余多少。下面的图示:

在这里插入图片描述

假设,offset 先为 0,公式就变成了 start = timestamp - ( timestamp + winSize )%winSize
那么,公式就变成了:

start = n*winSize + m - ( n*winSize + m + winSize )%winSize = n*winSize + m - ( (n+1)/winSize + m )%winSize

按照 % 取余这种运算逻辑,( (n+1)/winSize + m )%winSize= m ,最后,上面的公式为:

start = n*winSize,可见当没有 offset 的时候。start 是从整数开始的。举个例子,

1461756862000 这个是例子中的第一个 record 的时间戳。我们来看一下它是几点。我设置的 tumbling 时间是 3 s ,60%3 = 0 ,所以 3 是可以背 60 整除的,所以窗口的切割如下所示:

[ 0 , 3 ) , [ 3 , 6 ) , [ 6 , 9 ) ,[ 9 , 12 ), ... , [ n*3 , 3 + n*3 ] , 其中,n >= 0 .

timestamp 的整数不是落到那个区间的开始位置,这个元素就在这个区间。

然后,如果 offset 不等于 0 呢?不等于 0 ,不等于 0 的话,根据公式的推导,如下所示

 start = timestamp - ( timestamp + winSize )%winSize
         = n*winSize + m - ( n*winSize + m - offset + winSize )%winSize
         = n*winSize + m - ( (n+1)*winSize + m - offset )%winSize 
         = n*winSize + m - ( m - offset)
         = n*winSize + offset  

也就是如果我还是以 3 为窗口的大小,offset = 1 , 则得到的窗口划分如下所示:

[ 0 , 3 ) , [ 3 , 6 ) , [ 6 , 9 ) ,[ 9 , 12 ), ... , [ n*3 , 3 + n*3 ] , 其中,n >= 0 .

当 offset < winSize 的时候,window 的不同区间为在各个区间的开始和结束哪里加 1 .

[ 1 , 4 ) , [ 4 , 7 ) , [ 7 , 10 ) ,[ 10 , 13 ), ... , [ 1 + n*3 , 4 + n*3 ] , 其中,n >= 0 .

如果 offset > winSize ,则我们可以将 offset 分解为 offset = k*winSize + i ; i 为余数。则,根据上面的公式,假如 offset 为 4, 则 i = 1 ,k = 1 ,则 window 的不同分区为,

[ 4 , 7 ) , [ 7 , 10 ) , [ 10 , 13 ) ,[ 13 , 16 ), ... , [ 4 + n*3 , 7 + n*3 ] , 其中,n >= 0 .

ok 先把的窗口的划分讲清楚

<think>嗯,用户现在想了解Flink中的窗口类型及其用法。我之前已经回答了关于窗口机制和状态关联的内容,现在需要根据提供的引用材料来详细说明窗口类型。首先,我需要整理引用中提到的窗口类型。 引用[4]提到了滚动窗口和滑动窗口,其中滚动窗口是固定大小、无重叠的,而滑动窗口有滑动步长,可能重叠。另外,引用[5]里提到全局窗口和会话窗口,还有事件时间窗口。全局窗口是计数窗口底层实现,通常在自定义窗口时使用。会话窗口是基于活动间隔的,当一段时间没有数据到来时窗口关闭。 根据引用[2],窗口分配器分为键控流和非键控流,这可能影响窗口的应用方式。用户可能需要知道不同类型的窗口如何分配元素。引用[3]提到ProcessWindowFunction可以使用当前窗口的键控状态,这可能在窗口函数部分需要说明。 用户的问题是关于窗口类型及其用法,所以需要分门别类,详细解释每种窗口的定义、使用场景和代码示例。例如,滚动窗口适用于固定时间或数量统计,滑动窗口用于重叠统计,会话窗口处理不活跃时段,全局窗口用于自定义触发条件,计数窗口基于元素数量。 需要注意的是,用户提供的引用中有一些具体细节,比如全局窗口通过GlobalWindows.create()分配器使用,计数窗口是全局窗口的变种,需要自定义触发器和驱逐器。会话窗口通过动态间隙来划分,使用SessionWindowTimeGapExtractor。 另外,引用[5]中的驱逐器(Evictor)可能在滑动窗口或全局窗口中使用,用来过滤元素。这部分可能需要提到不同窗口类型如何结合驱逐器使用。 用户可能想知道每种窗口类型的适用场景,比如滚动窗口适合每小时统计,滑动窗口适合每分钟计算最近一小时的数据,会话窗口用于用户行为分析等。代码示例部分需要根据引用中的例子进行调整,比如使用Java API的示例,如.window(TumblingProcessingTimeWindows.of(Time.seconds(30)))。 还要注意窗口分配器的选择,键控流使用window,非键控流使用windowAll,这在引用[2]中提到过。需要指出不同类型的窗口是否支持键控流或非键控流。 最后,相关问题部分需要生成与窗口类型相关的问题,比如处理乱序数据、状态管理、性能优化等,参考之前的回答结构。</think>### Flink 窗口类型及用法详解 Flink窗口机制是流处理的核心功能,用于将无界数据流划分为有限的数据块(窗口),并支持聚合、统计等操作。以下是 Flink 的主要窗口类型及其典型用法: --- #### 一、按窗口划分方式分类 Flink 窗口分为 **时间驱动型** 和 **数据驱动型**,具体类型如下: ##### 1. **滚动窗口(Tumbling Window)** - **定义**:窗口有固定大小,**无重叠且不间隔**,每个数据仅属于一个窗口。 - **时间滚动窗口**:基于时间划分,例如每 5 秒统计一次。 ```java stream.keyBy(...) .window(TumblingProcessingTimeWindows.of(Time.seconds(5))) // 5秒时间滚动窗口 .sum(...); ``` - **计数滚动窗口**:基于元素数量划分,例如每 100 个元素统计一次。 ```java stream.keyBy(...) .countWindow(100) // 计数滚动窗口 .sum(...); ``` - **适用场景**:固定周期统计(如每分钟 PV、每小时销售额)[^4]。 ##### 2. **滑动窗口(Sliding Window)** - **定义**:窗口有固定大小和滑动步长,**允许重叠**,数据可能属于多个窗口。 - **时间滑动窗口**:例如窗口大小 10 秒,滑动步长 5 秒。 ```java stream.keyBy(...) .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5))) .sum(...); ``` - **计数滑动窗口**:例如窗口大小 100 个元素,滑动步长 50 个元素。 ```java stream.keyBy(...) .countWindow(100, 50) // 计数滑动窗口 .sum(...); ``` - **适用场景**:连续监控(如每分钟计算最近 10 秒的均值)[^4]。 ##### 3. **会话窗口(Session Window)** - **定义**:基于**活动间隙**动态划分窗口,若数据间的时间间隔超过阈值,则关闭当前窗口并开启新窗口。 ```java stream.keyBy(...) .window(EventTimeSessionWindows.withGap(Time.minutes(5))) // 5分钟无数据则关闭窗口 .process(...); ``` - 支持动态间隙配置: ```java .window(EventTimeSessionWindows.withDynamicGap(new SessionWindowTimeGapExtractor<T>() { @Override public long extract(T element) { return ...; // 根据元素动态返回间隙 } })); ``` - **适用场景**:用户行为分析(如用户会话超时检测)[^5]。 ##### 4. **全局窗口(Global Window)** - **定义**:所有数据分配到一个窗口,需**自定义触发器和驱逐器**才能输出结果(是计数窗口底层实现)。 ```java stream.keyBy(...) .window(GlobalWindows.create()) // 全局窗口 .trigger(...) // 必须自定义触发器 .evictor(...); // 可选驱逐器 ``` - **适用场景**:自定义复杂窗口逻辑(如仅处理特定条件的数据)[^1][^5]。 --- #### 二、按时间语义分类 根据时间语义,窗口可分为: 1. **事件时间窗口(Event Time Window)** - 基于数据自带的时间戳(需生成 Watermark 处理乱序)。 - **示例**: ```java stream.assignTimestampsAndWatermarks(...) // 分配时间戳和水位线 .keyBy(...) .window(TumblingEventTimeWindows.of(Time.seconds(10))); ``` 2. **处理时间窗口(Processing Time Window)** - 基于系统处理时间,无需处理乱序,但延迟敏感场景不适用。 --- #### 三、键控流 vs 非键控流 - **键控流窗口**: 使用 `.window()` 分配窗口,数据按 Key 分组后独立划分窗口。 ```java stream.keyBy(...) // 按 Key 分组 .window(...) // 分配窗口 .aggregate(...); ``` - **非键控流窗口**: 使用 `.windowAll()`,所有数据共享同一窗口(并行度为 1)。 ```java stream.windowAll(...) // 全局窗口(无 Key 分组) .process(...); ``` --- #### 四、窗口核心组件协同工作 1. **窗口分配器(WindowAssigner)**:定义如何分配数据到窗口(如滚动、滑动)[^2]。 2. **触发器(Trigger)**:决定何时触发窗口计算(如时间、数据量条件)[^5]。 3. **驱逐器(Evictor)**:在计算前/后过滤窗口数据(如保留最近 N 个元素)[^5]。 --- #### 五、典型代码示例 ```java // 事件时间滚动窗口(统计每10秒的订单总额) stream.assignTimestampsAndWatermarks( WatermarkStrategy.<Order>forBoundedOutOfOrderness(Duration.ofSeconds(2)) .withTimestampAssigner((order, ts) -> order.getEventTime()) ) .keyBy(Order::getProductId) .window(TumblingEventTimeWindows.of(Time.seconds(10))) .aggregate(new SumAmount()); // 全局窗口(每100个元素触发一次) stream.keyBy(...) .window(GlobalWindows.create()) .trigger(CountTrigger.of(100)) // 自定义计数触发器 .evictor(CountEvictor.of(100)) // 保留最后100个元素 .sum(...); ``` ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值