3. Watermark 概念:Watermark 是什么?如何表示数据流的进度?
好,咱们进入 Event Time 处理的核心——Watermark。
说实话,我刚接触 Flink 时,对 Watermark 的理解也绕了好一阵子。它不像窗口、算子那么直观。你想想看,数据流是源源不断的,系统怎么知道「某个时间点之前的数据都到齐了」?
答案是:它不知道。但 Watermark 给了它一个「合理的猜测」。
3.1 Watermark 的本质:一个时间戳断言
Watermark 是什么?说白了,它是一个插入到数据流中的特殊标记。这个标记携带一个时间戳 T,它的含义是:「我断言,所有事件时间小于等于 T 的数据,都已经到达了」。
注意,是「断言」,不是「保证」。在分布式系统中,你永远无法 100% 保证数据不会迟到。Watermark 只是告诉 Flink:「到这个点了,你可以放心地触发窗口计算了」。
核心理解:
- Watermark 是一个时间戳,不是一条普通数据
- 它表示「进度」,而不是「数据」
- 它告诉 Flink:在这个时间点之前的数据,大概率已经到齐了
我在项目中遇到过一位同事,他把 Watermark 理解成「数据的时间戳」,结果窗口一直不触发。嗯,这里要注意:Watermark 和数据的事件时间是两码事。
3.2 Watermark 如何表示数据流的进度?
数据流的进度,不能靠「等」,要靠「推」。Watermark 就是那个推动者。
我们来看一个简单的例子。假设数据流中有三条记录:
数据 A: 事件时间 = 10:00
数据 B: 事件时间 = 10:05
数据 C: 事件时间 = 10:03
如果 Watermark 策略是「当前最大事件时间减去 2 秒」,那么:
- 收到 A 后,Watermark = 10:00 - 2s = 09:58
- 收到 B 后,Watermark = 10:05 - 2s = 10:03
- 收到 C 后,Watermark = 10:05 - 2s = 10:03(因为最大事件时间还是 10:05)
当 Watermark 推进到 10:03 时,Flink 就知道:所有事件时间 ≤ 10:03 的数据都已经到了。这时候,如果有一个 [10:00, 10:05) 的窗口,就可以触发了。
我的习惯: 调试 Watermark 时,我会在 Flink Web UI 的 Watermark 监控页面上盯着看。如果 Watermark 一直不涨,大概率是数据源没设置事件时间,或者 Watermark 策略写错了。
3.3 Watermark 的两种生成方式
Flink 提供了两种 Watermark 生成方式,我分别说说。
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 周期性生成 | 每隔 N 毫秒生成一次 Watermark | 数据流比较稳定,延迟可控 |
| 断点式生成 | 每来一条数据都可能生成 Watermark | 数据流稀疏,或需要精确控制 |
周期性生成是默认方式,也是我推荐大多数场景使用的。你只需要实现 AssignerWithPeriodicWatermarks 接口,或者用 Flink 提供的 WatermarkStrategy 工具类。
// 周期性 Watermark 示例
DataStream<MyEvent> stream = env
.addSource(mySource)
.assignTimestampsAndWatermarks(
WatermarkStrategy.<MyEvent>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
);
这段代码的意思是:允许数据最多迟到 5 秒。Watermark 会随着数据不断推进,但始终比当前最大事件时间慢 5 秒。
我曾经踩过的坑: 把 forBoundedOutOfOrderness 的参数设得太大,比如 1 小时。结果窗口要等 1 小时才触发,业务方直接炸了。记住:这个参数是「容忍乱序的程度」,不是「数据延迟的保证」。设得越大,窗口触发越晚,实时性越差。
3.4 Watermark 的传播机制
Watermark 在算子之间是怎么传递的?
每个算子都会维护一个「当前 Watermark」。当它从上游收到新的 Watermark 时,会取所有输入分区的最小值作为自己的 Watermark,然后广播给下游。
为什么取最小值?因为只要有一个分区还没到,就不能断言全局进度。你想想看,如果两个分区分别推进到 10:00 和 10:05,取最小值 10:00 才是安全的。
3.5 Watermark 与窗口的关系
Watermark 和窗口是「搭档」关系。窗口定义了「对哪些数据做计算」,Watermark 决定了「什么时候触发计算」。
具体来说:
- 窗口的结束时间 = 窗口起始时间 + 窗口大小
- 当 Watermark ≥ 窗口结束时间时,窗口触发计算
- 触发后,窗口内的数据被处理,然后窗口关闭
举个例子,一个 [10:00, 10:05) 的滚动窗口:
- 窗口结束时间 = 10:05
- 当 Watermark 推进到 ≥ 10:05 时,窗口触发
- 所有事件时间在 [10:00, 10:05) 的数据都会被计算
避坑指南: 我曾经遇到一个场景,Watermark 一直不推进,窗口死活不触发。排查了半天,发现是数据源的时间戳字段解析错了,所有事件时间都是 1970-01-01。Watermark 策略基于这些错误的时间戳,自然永远达不到窗口的结束时间。所以,先确认事件时间解析正确,再调 Watermark。
3.6 总结一下
Watermark 是 Flink Event Time 处理的「进度条」。它告诉系统:到这个点了,之前的数据可以放心处理了。
记住三个关键点:
- Watermark 是一个断言,不是保证。它说「数据到齐了」,但实际可能还有迟到数据。
- Watermark 取所有分区的最小值,保证全局进度安全。
- Watermark 推进窗口触发,两者配合才能完成基于事件时间的计算。