Flink 2.3.0 从理论到实践 ------ 第 5 章 时间语义与 Watermark
课程定位:时间是 Flink 流处理的基础维度。本章深入 Event Time / Processing Time / Ingestion Time 三种时间语义、Watermark 的生成与传递机制、Flink 2.3 的 Watermark 对齐增强、迟到数据处理策略------这些是窗口触发、Checkpoint 推进、Join 同步的核心基础。
版本基线:Flink 2.3.0
章节导读
- [5.1 三种时间语义](#5.1 三种时间语义)
- [5.2 Watermark 机制](#5.2 Watermark 机制)
- [5.3 Watermark 生成策略](#5.3 Watermark 生成策略)
- [5.4 Watermark 传递与对齐](#5.4 Watermark 传递与对齐)
- [5.5 迟到数据处理](#5.5 迟到数据处理)
- [5.6 Flink 2.3 Watermark 对齐增强](#5.6 Flink 2.3 Watermark 对齐增强)
- [5.7 空闲源与 Watermark 生成](#5.7 空闲源与 Watermark 生成)
- [5.8 本章小结与下章预告](#5.8 本章小结与下章预告)
5.1 三种时间语义
Flink 流处理中"时间"有三个不同来源:
5.1.1 时间语义定义
事件发生 进入 Flink 算子处理
│ │ │
▼ ▼ ▼
Event Time Ingestion Time Processing Time
(事件时间) (摄入时间) (处理时间)
| 时间语义 | 定义 | 特点 | 适用场景 |
|---|---|---|---|
| Event Time | 事件实际发生的时间(业务时间) | 确定性高,重现结果一致 | 业务报表、窗口聚合 |
| Processing Time | 算子处理该事件的时间(机器时间) | 延迟最低,但不确定 | 监控告警、实时大屏 |
| Ingestion Time | 事件进入 Flink 的时间 | 介于两者之间 | 少用,已被 Event Time 替代 |
5.1.2 选型建议
| 场景 | 推荐 | 理由 |
|---|---|---|
| 业务报表 | Event Time | 重算结果一致(回放历史数据时) |
| 实时大屏 | Processing Time | 延迟最低,不需要处理乱序 |
| 窗口聚合 | Event Time | 防止数据乱序导致窗口错乱 |
| CEP 模式匹配 | Event Time | 基于事件发生时间检测 |
| 简单监控 | Processing Time | 实时性优先 |
项目硬约束 :车联网行程数仓使用 Event Time(
collect_time字段),保证离线回算与实时计算结果一致。
5.1.3 配置
java
// DataStream API
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
// Flink 2.x 推荐: 显式在 Source 上指定
WatermarkStrategy<Event> strategy = WatermarkStrategy
.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, ts) -> event.getCollectTime() * 1000);
DataStream<Event> stream = env
.fromSource(kafkaSource, strategy, "kafka-source");
sql
-- Flink SQL: 在 DDL 中指定
CREATE TABLE kafka_source (
vin STRING,
collect_time BIGINT, -- 秒级时间戳
event_time AS TO_TIMESTAMP_LTZ(collect_time, 0), -- 派生事件时间
WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (...);
5.2 Watermark 机制
5.2.1 Watermark 是什么
Watermark(水位线) 是事件时间进度的信号,表示"时间戳 ≤ Watermark 的数据已经全部到达"。
事件流: [10:00] [10:01] [10:03] [10:02] [10:05] [10:04] ...
↑ 乱序数据(10:02 比 10:03 晚到)
Watermark: ── 10:00 ──── 10:01 ──── 10:03 ──── 10:03 ──── 10:05 ──── 10:05 ──►
↑ Watermark 推进到 10:03
表示 10:03 之前的数据都已到达
10:02 被视为"迟到数据"
5.2.2 Watermark 与窗口的关系
窗口: [10:00, 10:05) 触发条件: Watermark >= 10:05
事件: [10:01] [10:02] [10:04] [10:03] [10:06] Watermark
│ │ │
▼ ▼ ▼
窗口内 窗口内 推进到 10:05 → 触发计算
关键:Watermark 决定窗口何时触发,而不是数据到达决定。
5.2.3 Watermark 的角色
| 角色 | 说明 |
|---|---|
| 窗口触发 | 窗口结束时间 ≤ Watermark 时触发计算 |
| 迟到判定 | 事件时间 < 当前 Watermark 的数据为"迟到" |
| Join 同步 | 双流 Join 的两侧通过 Watermark 协调 |
| Checkpoint 推进 | Checkpoint barrier 与 Watermark 独立 |
5.3 Watermark 生成策略
Flink 2.x 推荐使用 WatermarkStrategy 统一接口生成 Watermark。
5.3.1 三种内置策略
| 策略 | API | 适用场景 |
|---|---|---|
| 单调递增 | forMonotonousTimestamps() |
事件严格有序 |
| 有界乱序 | forBoundedOutOfOrderness(Duration) |
允许一定乱序(最常用) |
| 自定义 | forGenerator(WatermarkGenerator) |
复杂逻辑 |
5.3.2 有界乱序策略(推荐)
java
WatermarkStrategy<Event> strategy = WatermarkStrategy
// 允许 5 秒乱序
.forBoundedOutOfOrderness(Duration.ofSeconds(5))
// 从事件中提取时间戳(必须返回毫秒)
.withTimestampAssigner((event, ts) -> event.getCollectTime() * 1000);
DataStream<Event> stream = env
.fromSource(kafkaSource, strategy, "kafka-source");
sql
-- SQL 等价
WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
5.3.3 延迟选择
| 场景 | 推荐延迟 | 说明 |
|---|---|---|
| Kafka 同分区有序 | 0-1 秒 | 单分区按 key 有序 |
| Kafka 跨分区乱序 | 5-10 秒 | 跨分区合并乱序 |
| 网络延迟大 | 30-60 秒 | 4G/弱网环境 |
| 历史回放 | 大延迟 | 容忍大乱序 |
项目经验:车联网 Kafka 报文场景用 5 秒延迟,平衡实时性与正确性。
5.3.4 自定义策略示例
java
public class VehicleWatermarkGenerator implements WatermarkGenerator<Event> {
private long maxTimestamp = Long.MIN_VALUE;
private long lastWatermark = Long.MIN_VALUE;
private final long maxOutOfOrderness = 5000; // 5 秒
@Override
public void onEvent(Event event, long ts, WatermarkOutput output) {
maxTimestamp = Math.max(maxTimestamp, event.getCollectTime() * 1000);
}
@Override
public void onPeriodicEmit(WatermarkOutput output) {
long newWatermark = maxTimestamp - maxOutOfOrderness;
// Watermark 只能单调递增
if (newWatermark > lastWatermark) {
lastWatermark = newWatermark;
output.emitWatermark(new Watermark(newWatermark));
}
}
}
5.4 Watermark 传递与对齐
5.4.1 传递机制
Watermark 在算子间传递遵循最小值原则:一个算子的 Watermark = 所有输入 Watermark 的最小值。
输入 A: ── 10:00 ──── 10:05 ──── 10:10 ──►
输出:
输入 B: ── 10:00 ──── 10:02 ──── 10:05 ──► 取 min(A, B)
── 10:00 ── 10:02 ── 10:05 ──►
5.4.2 多输入算子的 Watermark
┌─ Source A (WM = 10:10) ─────┐
keyBy ──► ├─ Source B (WM = 10:05) ─────┤ ──► 算子 WM = min(10:10, 10:05) = 10:05
└─ Source C (WM = 10:08) ─────┘
关键问题:如果 Source A/B/C 推进速度不一,慢的源会"拖累"整体 Watermark 推进,导致下游窗口迟迟不触发------这正是 Flink 2.3 Watermark 对齐要解决的问题。
5.5 迟到数据处理
当事件时间 < 当前 Watermark 时,该事件被视为"迟到数据"。Flink 提供三种处理方式。
5.5.1 三种处理方式
| 方式 | API | 说明 |
|---|---|---|
| 丢弃 | 默认 | 迟到数据直接丢弃 |
| Allowed Lateness | .allowedLateness(Duration) |
允许迟到数据更新已触发窗口 |
| Side Output | .sideOutputLateTag() |
迟到数据送到侧输出流 |
5.5.2 完整示例
java
OutputTag<Event> lateEvents = new OutputTag<Event>("late-events") {};
DataStream<Stats> result = stream
.keyBy(Event::getVin)
// 5 分钟滚动窗口
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
// 允许 1 分钟迟到
.allowedLateness(Time.minutes(1))
// 迟到超 1 分钟的数据送到侧输出
.sideOutputLateData(lateEvents)
.aggregate(new StatsAggregator());
// 获取迟到数据流
DataStream<Event> lateStream = result.getSideOutput(lateEvents);
5.5.3 处理流程
事件到达
│
▼
事件时间 >= 窗口结束 + Watermark? ──是──► 正常处理
│
否
▼
事件时间 >= 窗口结束 + Watermark - allowedLateness? ──是──► 更新窗口(已触发)
│
否
▼
送 Side Output(迟到数据流)
项目经验:车联网报文迟到 5 秒以内直接丢弃(业务可接受),超长迟到(如断流后补传)走 Side Output 落 Paimon 离线表回算。
5.6 Flink 2.3 Watermark 对齐增强
5.6.1 问题场景
多源 Join 场景下,快源 Watermark 推进远超慢源:
Source A (快): ── 10:00 ──── 10:10 ──── 10:20 ──── 10:30 ──►
Source B (慢): ── 10:00 ──── 10:01 ──── 10:02 ──────────►
下游 WM = min(A, B) = 10:02
→ A 的 10:02~10:30 数据在下游算子堆积(等待 B 追上)
→ 状态膨胀 + 网络缓冲耗尽
5.6.2 Watermark 对齐机制
Flink 2.3 引入 Watermark 对齐:快源临时降速,等待慢源追上。
开启对齐:
Source A (快): ── 10:00 ── 10:10 ──[降速]── 10:12 ──[等]── 10:12 ──►
Source B (慢): ── 10:00 ── 10:05 ── 10:08 ── 10:12 ──────────────►
下游 WM = 10:12(两侧对齐)
→ 减少下游堆积
5.6.3 配置
java
WatermarkStrategy<Event> strategy = WatermarkStrategy
.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, ts) -> event.getCollectTime() * 1000)
// Watermark 对齐(2.3 新特性)
.withWatermarkAlignment(
"kafka-source-group", // 对齐组
Duration.ofSeconds(20), // 最大偏差(快源领先慢源不超过 20 秒)
Duration.ofSeconds(5) // 更新间隔
);
DataStream<Event> stream = env
.fromSource(kafkaSource, strategy, "kafka-source");
yaml
# flink-conf.yaml(全局默认)
pipeline.watermark-alignment.enabled: true
pipeline.watermark-alignment.group: default
pipeline.watermark-alignment.max-drift: 20s
pipeline.watermark-alignment.update-interval: 5s
5.6.4 适用场景
| 场景 | 是否启用 | 理由 |
|---|---|---|
| 单源 | 否 | 无对齐需求 |
| 多源 Join | ✅ | 避免快源数据在下游堆积 |
| 多源聚合 | ✅ | 避免状态膨胀 |
| 同构多分区 | 可选 | 同分区 watermark 接近 |
5.7 空闲源与 Watermark 生成
5.7.1 空闲源问题
某些 Source 长时间无数据(如某 Kafka 分区无数据),Watermark 不推进,导致下游窗口迟迟不触发。
Source A: ── 10:00 ── 10:05 ── 10:10 ────[空闲]──────────►
Source B: ── 10:00 ── 10:05 ── 10:10 ── 10:15 ── 10:20 ──►
下游 WM = min(A, B) = 10:10(A 卡住)
→ B 的 10:15+ 数据在下游堆积,窗口不触发
5.7.2 空闲超时配置
java
WatermarkStrategy<Event> strategy = WatermarkStrategy
.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, ts) -> event.getCollectTime() * 1000)
// 60 秒无数据则视为空闲,不参与 Watermark 计算
.withIdleness(Duration.ofSeconds(60));
DataStream<Event> stream = env
.fromSource(kafkaSource, strategy, "kafka-source");
5.7.3 空闲 vs 对齐
| 机制 | 解决问题 | 副作用 |
|---|---|---|
| 空闲超时 | 某源长期无数据卡住 | 空闲源数据可能迟到 |
| Watermark 对齐 | 多源推进速度不一 | 快源临时降速 |
生产建议:两者结合------空闲超时 60 秒 + 对齐偏差 20 秒,覆盖大多数场景。
5.8 本章小结与下章预告
本章小结
┌────────────────────────────────────────────────────────────────┐
│ 第 5 章 要点回顾 │
└────────────────────────────────────────────────────────────────┘
✓ 三种时间语义:
Event Time(业务时间,确定性高)
Processing Time(机器时间,延迟低)
Ingestion Time(摄入时间,少用)
✓ Watermark = 事件时间进度信号
表示 "<= WM 的数据已全部到达"
决定窗口触发 / 迟到判定 / Join 同步
✓ 生成策略:
单调递增(严格有序)
有界乱序(最常用,延迟 5-10 秒)
自定义(复杂逻辑)
✓ 传递: 取所有输入 WM 的最小值
问题: 慢源拖累整体
✓ 迟到数据处理:
丢弃(默认)
Allowed Lateness(更新已触发窗口)
Side Output(送到侧输出流)
✓ Flink 2.3 新特性:
Watermark 对齐(快源降速等慢源)
空闲超时(60s 无数据不参与 WM 计算)
✓ 生产配置:
Event Time + 有界乱序 5s
+ 对齐偏差 20s + 空闲超时 60s
下章预告
第 6 章 Checkpoint 与容错:讲解 Checkpoint 的 Chandy-Lamport 算法、Barrier 对齐机制、Checkpoint 配置参数、Exactly-Once 的两阶段提交(2PC)、Savepoint 与 Checkpoint 的区别、故障恢复与重启策略。Checkpoint 是 Flink 容错的核心,与本章 Watermark 共同保证流处理的正确性。
官方参考资料
- Flink 时间语义:https://nightlies.apache.org/flink/flink-docs-stable/docs/concepts/time/
- Watermark 策略:https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/datastream/event-time/watermark_strategies/
- Watermark 对齐(2.3):https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/datastream/event-time/watermark_strategies/#watermark-alignment-
- 迟到数据:https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/datastream/event-time/streaming_query/