为什么需要窗口
无界流的数据永无止境,无法直接对「全部数据」做聚合(和不会收敛)。窗口把无限流切分为有限块,在每个块上单独计算。
无界流:──A──B──C──D──E──F──G──H──I──J──K──L──...(永不结束)
加 1 分钟滚动窗口后:
窗口 1: [A, B, C] → 聚合结果 1
窗口 2: [D, E, F, G] → 聚合结果 2
窗口 3: [H, I, J] → 聚合结果 3
...
四种窗口类型
1. 滚动窗口(Tumbling Window)
窗口之间不重叠,每个事件只属于一个窗口。
时间线:|----1min----|----1min----|----1min----|----1min----|
事件: A B C D E F G H I J
窗口: [A,B] [C,D,E] [F,G] [H,I,J]
适用:分钟级/小时级固定统计(每分钟订单量、每小时 UV)。
2. 滑动窗口(Sliding Window)
窗口之间有重叠,一个事件可能属于多个窗口。
窗口大小 5 分钟,滑动步长 1 分钟:
时间线:|1m|1m|1m|1m|1m|1m|1m|1m|...
窗口: [-------5min------]
[-------5min------]
[-------5min------]
适用:移动平均(近 5 分钟平均延迟)、趋势平滑。
3. 会话窗口(Session Window)
由活动间隙动态划分,没有固定大小。
事件流:A──B────C──D──E───────F──G
↑ 间隙 > 30min ↑ 间隙 > 30min
窗口:[A,B] [C,D,E] [F,G]
适用:用户行为分析(一次会话内的点击序列)。
4. 全局窗口(Global Window)
只有一个窗口,包含所有数据。
窗口:[A, B, C, D, E, F, G, H, I, J, ...]
适用:需要自定义触发条件的场景(如「收集 100 条后触发")。
Flink 窗口 API
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// KafkaSource 是 Source 接口实现,走 fromSource;addSource 已在 1.20 废弃
DataStream<Event> stream = env.fromSource(
kafkaSource,
WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, ts) -> event.getEventTime()),
"kafka");
// 滚动窗口(Event Time)
stream.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
.aggregate(new CountAggregate());
// 滑动窗口
stream.keyBy(Event::getUserId)
.window(SlidingEventTimeWindows.of(Duration.ofMinutes(5), Duration.ofMinutes(1)))
.aggregate(new AverageAggregate());
// 会话窗口
stream.keyBy(Event::getUserId)
.window(EventTimeSessionWindows.withGap(Duration.ofMinutes(30)))
.aggregate(new SessionAggregate());
// 全局窗口 + 自定义触发器
stream.keyBy(Event::getUserId)
.window(GlobalWindows.create())
.trigger(CountTrigger.of(100))
.aggregate(new BatchAggregate());
触发器(Trigger)
窗口的触发器决定何时输出结果:
| 触发器 | 行为 |
|---|---|
EventTimeTrigger | 当 Watermark 越过窗口的 maxTimestamp(结束时间减 1ms)时触发(默认) |
ProcessingTimeTrigger | 处理时间到达窗口结束时间时触发 |
CountTrigger | 窗口内元素数量达到阈值时触发 |
ContinuousEventTimeTrigger | 每隔一段时间触发一次(窗口内的增量更新) |
// 每分钟触发一次(窗口未关闭时也输出)
stream.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(5)))
.trigger(ContinuousEventTimeTrigger.of(Duration.ofMinutes(1)))
.aggregate(new CountAggregate());
这个例子:5 分钟窗口,但每分钟输出一次当前计数(增量更新)。窗口关闭时再输出最终值。
驱逐器(Evictor)
驱逐器在窗口触发前移除部分元素:
stream.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
.evictor(TimeEvictor.of(Duration.ofSeconds(30))) // 只保留最近 30 秒的数据
.aggregate(new CountAggregate());
适用:只关心窗口内的「最新」数据,丢弃过期元素。
fired-but-not-closed
Flink 窗口的微妙语义:触发(fire)≠ 关闭(close)。
- fire:输出当前窗口的计算结果
- close:窗口状态被清理,不再接受新数据
默认行为:Watermark 超过窗口结束时间 → fire + close 同时发生。
如果配置了 allowedLateness:
stream.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
.allowedLateness(Duration.ofSeconds(30)) // 允许迟到 30 秒
.aggregate(new CountAggregate());
行为:
Watermark 到达 06:01:00 → 窗口 [06:00, 06:01) fire,输出结果
→ 窗口保持 open(因为 allowedLateness = 30s)
Watermark 到达 06:01:30 → 窗口 close,状态清理
在 fire 和 close 之间的 30 秒内,迟到的数据还会被计入该窗口并再次输出。机制在 EventTimeTrigger 里:Watermark 已经越过 maxTimestamp 时,它收到元素会直接返回 FIRE。所以这是 allowedLateness 自带的重新触发,不需要配 ContinuousEventTimeTrigger;后者管的是「窗口还没结束,也要按固定间隔先输出一次」,两者不要混为一谈。
窗口在 Event Time vs Processing Time 下的差异
| 场景 | Event Time | Processing Time |
|---|---|---|
| 窗口分配 | 按事件时间戳 | 按处理时的系统时间 |
| 乱序容忍 | Watermark + allowedLateness | 无乱序(按到达顺序) |
| 结果一致性 | 可复现(重跑相同数据得相同结果) | 不可复现(取决于处理速度) |
| 延迟 | 可能因等待 Watermark 而延迟 | 立即输出 |
eventd v3 使用 Event Time + 1 分钟滚动窗口 + 10 秒 allowedLateness:
stream.assignTimestampsAndWatermarks(
WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(10))
.withIdleness(Duration.ofMinutes(5))
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
)
.keyBy(Event::getSymbol)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
.allowedLateness(Duration.ofSeconds(10))
.aggregate(new OrderCountAggregate());
小结
| 窗口类型 | 特点 | 适用 |
|---|---|---|
| 滚动 | 不重叠,固定大小 | 固定周期统计 |
| 滑动 | 有重叠,固定大小 | 移动平均、趋势分析 |
| 会话 | 动态大小,按间隙划分 | 用户行为、会话分析 |
| 全局 | 只有一个窗口 | 自定义触发条件 |
| 概念 | 作用 |
|---|---|
| Trigger | 决定何时输出结果 |
| Evictor | 触发前过滤窗口内元素 |
| allowedLateness | 容忍迟到数据的时间 |
下一节 1.3 Flink 环境搭建 :MiniCluster、Maven 配置、第一个可运行 Flink 作业。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。