Flink SQL 窗口表值函数TUMBLE / HOP / CUMULATE / SESSION

1. 一张图理解"窗口"这件事

先看下面这张图(就是你提供的那张):

图里的紫色小圆点代表事件,横轴是时间。

你能看到好几层彩色矩形(window1 ~ window6):

  • 红色窄窗口:window1window4
  • 青绿色中等宽度窗口:window2window5
  • 紫色大窗口:window3window6

它们有两个关键特征:

  1. 窗口之间会重叠 :比如紫色的 window3 覆盖了 window1window2 的范围;
  2. 同一个事件可以属于多个窗口:一颗紫色点,只要落在某个窗口的时间范围内,就会被分配进去。

这其实就是 Flink 里 HOP / CUMULATE 这类窗口的核心直觉:

  • 对于 HOP(滑动窗口):同一个起点向右不断滑动,窗口大小固定,但相邻窗口会重叠;
  • 对于 CUMULATE(累积窗口):起点不动,不断向右扩展窗口终点,窗口大小越来越大,也必然重叠。

理解了这张图,再看后面的 SQL 会轻松很多。


2. Windowing TVF 的统一模型

四种窗口 TVF 的共同点:

sql 复制代码
TUMBLE / HOP / CUMULATE / SESSION (TABLE data, ...)
  • 输入 :一张包含时间字段的表 data

  • 输出:一张"新表",包含:

    • 原表所有字段;

    • 额外 3 列:

      • window_start
      • window_end
      • window_time

其中:

  • 流模式 下,window_time 是一个真正的时间属性,可以继续用于窗口、interval join、over 聚合等;
  • 批模式 下,window_timeTIMESTAMP / TIMESTAMP_LTZ 类型;
  • window_time 的值总是:window_end - 1ms(可以简单理解为"窗口的最后一刻")。

同时,输入里的时间属性列(比如 bidtime)在通过窗口 TVF 之后,会变成普通时间戳列,真正代表窗口时间的是 window_* 这三列。

有了这三列,我们就可以:

  • 在窗口粒度上做 GROUP BY
  • 做窗口级的 TopN;
  • 用窗口时间做 Join;
  • 再套一层窗口 TVF......

这也是 Windowing TVF 比老式 Grouped Window 强大的根本原因:先把"时间"变成显式字段,再用标准 SQL 玩出花来。

3. TUMBLE:固定大小、不重叠的滚动窗口

3.1 语义 & 典型场景

TUMBLE 是最基础的窗口:固定大小、不重叠,一条记录只属于一个窗口。

适用场景:

  • 每 5 分钟统计一次订单金额;
  • 每小时计算一次活跃用户数;
  • 对离线批数据做按天 / 按小时聚合。

3.2 基本语法

sql 复制代码
TUMBLE(TABLE data, DESCRIPTOR(timecol), size [, offset])

参数说明:

  • data:任意包含时间属性列的表;
  • timecol:要用来划分窗口的时间属性列;
  • size:窗口大小(例如 INTERVAL '10' MINUTES);
  • offset:可选,窗口起点偏移。

3.3 示例:对 Bid 表做 10 分钟滚动窗口汇总

先看表结构与原始数据:

sql 复制代码
DESC Bid;

+-------------+------------------------+------+-----+--------+---------------------------------+
|        name |                   type | null | key | extras |                       watermark |
+-------------+------------------------+------+-----+--------+---------------------------------+
|     bidtime | TIMESTAMP(3) *ROWTIME* | true |     |        | `bidtime` - INTERVAL '1' SECOND |
|       price |         DECIMAL(10, 2) | true |     |        |                                 |
|        item |                 STRING | true |     |        |                                 |
+-------------+------------------------+------+-----+--------+---------------------------------+

SELECT * FROM Bid;

+------------------+-------+------+
|          bidtime | price | item |
+------------------+-------+------+
| 2020-04-15 08:05 |  4.00 | C    |
| 2020-04-15 08:07 |  2.00 | A    |
| 2020-04-15 08:09 |  5.00 | D    |
| 2020-04-15 08:11 |  3.00 | B    |
| 2020-04-15 08:13 |  1.00 | E    |
| 2020-04-15 08:17 |  6.00 | F    |
+------------------+-------+------+

把它丢进 TUMBLE:

sql 复制代码
SELECT *
FROM TUMBLE(TABLE Bid, DESCRIPTOR(bidtime), INTERVAL '10' MINUTES);

得到:

text 复制代码
bidtime              price  item  window_start        window_end          window_time
-----------------------------------------------------------------------------------------
2020-04-15 08:05     4.00   C     2020-04-15 08:00    08:10               08:09:59.999
2020-04-15 08:07     2.00   A     2020-04-15 08:00    08:10               08:09:59.999
2020-04-15 08:09     5.00   D     2020-04-15 08:00    08:10               08:09:59.999
2020-04-15 08:11     3.00   B     2020-04-15 08:10    08:20               08:19:59.999
2020-04-15 08:13     1.00   E     2020-04-15 08:10    08:20               08:19:59.999
2020-04-15 08:17     6.00   F     2020-04-15 08:10    08:20               08:19:59.999

在此基础上做窗口聚合就很自然了:

sql 复制代码
SELECT window_start, window_end, SUM(price) AS total_price
FROM TUMBLE(TABLE Bid, DESCRIPTOR(bidtime), INTERVAL '10' MINUTES)
GROUP BY window_start, window_end;

4. HOP:滑动窗口 / 跳跃窗口(与图示强相关)

4.1 语义

HOP 也是固定窗口大小,但多了一个概念:滑动步长 slide

  • size:窗口的覆盖时长;
  • slide:相邻窗口起点之间的间隔;
  • slide < size 时,窗口之间会重叠,一条记录可能属于多个窗口。

这就是图里那种多层彩色矩形 的情况:

比如 size=10min, slide=5min 时,一条落在 08:07 的事件会被放进:

  • 08:00, 08:10)

两个窗口里。

4.2 基本语法

sql 复制代码
HOP(TABLE data, DESCRIPTOR(timecol), slide, size [, offset])

4.3 示例:5 分钟滑动 10 分钟窗口

sql 复制代码
SELECT *
FROM HOP(TABLE Bid,
         DESCRIPTOR(bidtime),
         INTERVAL '5' MINUTES,  -- slide
         INTERVAL '10' MINUTES); -- size

结果里你会看到每条记录出现两次(除了边缘):

text 复制代码
bidtime   price item window_start       window_end         window_time
---------------------------------------------------------------------------
08:05     4.00  C    08:00              08:10              08:09:59.999
08:05     4.00  C    08:05              08:15              08:14:59.999
...

配上你给的那张图来理解:

  • 红框可以看作"窄窗口"(size 小,slide 等于 size,不重叠);
  • 青框可以看作"稍大一点的窗口";
  • 紫框则是"覆盖更长时间的窗口"。

真正的 HOP 是"窗口大小固定、起点按 slide 平移",所以如果你用图里的方式画:

  • window1window2window3 的起点依次往右移;
  • 它们之间会像图中那样层层重叠。

这也是为什么在滑动窗口统计里,我们经常会看到同一条记录被重复计算,这本身就是语义要求(比如"最近 10 分钟指标,每 5 分钟更新一次")。

5. CUMULATE:从小到大的累积窗口(图片里的"套娃窗口")

再回到图里那几层彩色矩形:

  • 同一时间起点左边对齐;
  • 右边一个比一个长;
  • 同一批事件会出现在多个"越来越大"的窗口中。

这就是 CUMULATE 最典型的视觉效果。

5.1 语义

CUMULATE 的设计目标是:在一个更大的时间范围里,按固定步长输出一系列"从小到大的累计窗口"

你可以把它理解成:

  1. 先按 size 做一个滚动窗口;
  2. 在每个滚动窗口内部,从 window_start 开始,每隔 step 扩大一次右边界,生成一个新窗口。

因此:

  • step:累积增长的步长;
  • size:最终最大的窗口宽度,必须是 step 的整数倍。

典型场景:

  • 每分钟展示"当天到当前时刻"的累计 UV;
  • 每小时展示"本周当前小时之前"的累计 GMV。

5.2 基本语法

sql 复制代码
CUMULATE(TABLE data, DESCRIPTOR(timecol), step, size [, offset])

5.3 示例:2 分钟步长,10 分钟最大窗口

sql 复制代码
SELECT *
FROM CUMULATE(TABLE Bid,
              DESCRIPTOR(bidtime),
              INTERVAL '2' MINUTES,   -- step
              INTERVAL '10' MINUTES); -- size

结果非常有"套娃感":

  • 对于 08:00 开始的窗口,会产生:

    • 08:00, 08:06)

    • 08:00, 08:10)

这对应到你那张图里,从左侧同一个起点不断向右拉长的多个矩形。

这样在做统计时就可以得到"从短到长"的一系列累计值,非常适合监控、看板类场景。

6. SESSION:按行为"自然分段"的会话窗口

上面几种窗口都是时间驱动:每过一段时间就切一次。

SESSION 走的思路完全不同:它是事件驱动的------只要长时间没有事件,就认为上一个"会话"已经结束。

6.1 语义

配置一个 gap,比如 10 分钟:

  • 对同一个 key(比如用户、设备):

    • 两条事件之间的间隔 < 10 分钟 → 认为属于同一个 Session;
    • 间隔 ≥ 10 分钟 → 前一个 Session 结束,新的 Session 开启。
  • 会话窗口没有固定大小,也没有固定起止时间,结束时间取决于最后一条事件的时间戳 + gap。

6.2 基本语法

sql 复制代码
SESSION(TABLE data [PARTITION BY (keycols, ...)],
        DESCRIPTOR(timecol),
        gap)
  • PARTITION BY 可选,但实际业务中几乎都会按 user / device 做分区会话。

6.3 示例:按 item 维度开 5 分钟会话窗口

sql 复制代码
SELECT window_start, window_end, item, SUM(price) AS total_price
FROM SESSION(
       TABLE Bid PARTITION BY item,
       DESCRIPTOR(bidtime),
       INTERVAL '5' MINUTES
     )
GROUP BY item, window_start, window_end;

你会看到同一个 item 的多条事件,被聚成一个"自然会话"的窗口,而另一些分散在更远的地方,就被拆成多个 Session。

需要注意的是:

  • 当前 Session TVF 只支持流模式
  • 相关优化还在演进中,性能调优上暂时没有太多花活,但语义已经非常好用。

7. 窗口 Offset:让窗口对齐业务时间

很多真实业务的"自然边界"并不是 00:00:

  • 跨国业务需要按照目标时区的 0 点对齐;
  • 某些业务日从每天早上 8 点开始;
  • 某些报表希望从 9:30 开盘开始对齐......

这时候就可以用窗口 TVF 的 offset 参数来平移窗口的起点。

7.1 例子:10 分钟滚动窗口,整体向右偏移 1 分钟

sql 复制代码
SELECT *
FROM TUMBLE(
       TABLE Bid,
       DESCRIPTOR(bidtime),
       INTERVAL '10' MINUTES,
       INTERVAL '1' MINUTES   -- offset
     );

这会让窗口从 08:01、08:11、08:21... 开始:

text 复制代码
window_start   window_end
-----------------------------
08:01          08:11
08:11          08:21
...

同样的时间戳,会被分配到跟默认 offset=0 完全不同的窗口。

需要记住两点:

  1. offset 可以是正数,也可以是负数,本质就是改变窗口对齐点
  2. offset 只影响窗口划分,不影响 Watermark

8. 选什么窗口?一些实践经验

最后简单给几个"选型小抄":

  1. 固定时间粒度报表 → TUMBLE

    • 每 5 分钟 / 每小时 / 每天一份完整报表;
    • 和离线数仓里的"按天按小时汇总"思路最接近。
  2. 最近 N 时间的实时指标 → HOP

    • "最近 10 分钟 PV,每 1 分钟更新一次";
    • "最近 1 小时错误率,每 5 分钟更新一次"。
  3. 阶梯式累加曲线 → CUMULATE

    • 实时看板、运营大屏上常见的"从零开始一路累积"的折线;
    • 典型配置是 step=1min, size=1day
  4. 用户行为会话分析 → SESSION

    • UV、停留时长、单次会话浏览深度等;
    • 通常配合 PARTITION BY user_id 使用。
  5. 窗口边界对不齐业务时间 → offset

    • 业务日 08:00 对齐;
    • 不同国家时区统一口径。

9. 小结

Flink SQL 的窗口表值函数把"时间"这件事情做了一个非常漂亮的抽象:

  • TUMBLE / HOP / CUMULATE / SESSION 这四种 TVF,把无限流切成了各种形态的时间片;
  • window_start / window_end / window_time 三个字段,把原本隐含在算子里的时间语义显式化;
  • 在这个基础上,你可以用纯 SQL 的方式,构建窗口聚合、窗口 TopN、窗口 Join、窗口去重等一整套复杂计算。

配合那张"多层彩色窗口"的图片来看,你可以把它记成一个简单的心智模型:

  • TUMBLE:一层不重叠的块;
  • HOP:同尺寸的块不断滑动,层层叠加;
  • CUMULATE:同一个起点的块越拉越长,像套娃一样;
  • SESSION:按用户行为自然分段,块的长短由数据说了算。

掌握了这一套,你在 Flink 上遇到绝大多数"时间 + 聚合"的问题,都能找到对应的窗口形式来解决。

相关推荐
想不明白的过度思考者1 小时前
Spring IoC 与 DI 深度剖析:从“控制反转”到 Bean 的集中管理
java·spring·ioc·di
南河的南1 小时前
解决IDEA无法下载Maven仓库的源码
java·maven·intellij-idea
无名-CODING1 小时前
#Servlet与Tomcat完全指南 - 从入门到精通(含面试题)
java·servlet·tomcat
想个名字太难1 小时前
ElasticSearch编程操作
java·elasticsearch·全文检索
小马爱打代码1 小时前
Spring AI:RAG 增强检索介绍
java·人工智能·spring
Franciz小测测1 小时前
Python APScheduler 定时任务 独立调度系统设计与实现
java·数据库·sql
天一生水water2 小时前
Eclipse数值模拟软件详细介绍(油藏开发的“工业级仿真引擎”)
java·数学建模·eclipse
梦里不知身是客112 小时前
flink从kafka读取数据
flink·kafka·linq
谷粒.4 小时前
Cypress vs Playwright vs Selenium:现代Web自动化测试框架深度评测
java·前端·网络·人工智能·python·selenium·测试工具