可以把 Watermark 理解为开发者向 Flink 下达的一个推进其内部事件时间时钟的指令。时钟推进的速度和准确性,取决于对自己数据流乱序特性的了解程度。所谓的"保证",其实是开发者和 Flink 运行时之间基于定义的策略而达成的一种"契约"。
Flink Watermark 的工作机制可以分为两个核心部分:
- Watermark 的时间是如何确定的?
- 它如何能"保证"在这之前的所有事件都已经到达了?
Watermark 的时间是如何确定的?
Watermark 的时间戳不是 由 Flink 自动猜测的,而是由用户定义的逻辑 来生成的。这个逻辑体现在一个叫做 WatermarkGenerator
的组件中。开发者需要根据数据流的特性提供生成 Watermark 的具体算法。
这个过程通常包含两个步骤:
-
时间戳分配 (Timestamp Assignment) :首先,通过
TimestampAssigner
从每条事件中提取其业务时间(Event Time)。这通常是事件数据本身的一个字段,比如event.getCreationTime()
。 -
Watermark 生成 (Watermark Generation) :
WatermarkGenerator
会观察这些事件的时间戳,并根据其内部逻辑来决定何时生成新的 Watermark,以及这个 Watermark 的时间戳应该是多少。
WatermarkGenerator
接口定义了两个核心方法:
java
// ... existing code ...
@Public
public interface WatermarkGenerator<T> {
/**
* 每个事件都会调用此方法,允许 Watermark 生成器检查并记录事件时间戳,
* 或者基于事件本身发出一个 Watermark。
*/
void onEvent(T event, long eventTimestamp, WatermarkOutput output);
/**
* 定期调用此方法,可能会发出一个新的 Watermark,也可能不发。
*
* <p>此方法的调用周期取决于 {@link
* ExecutionConfig#getAutoWatermarkInterval()} 的配置。
*/
void onPeriodicEmit(WatermarkOutput output);
}
为了方便使用,Flink 内置了几种常见的生成策略:
-
单调递增时间戳 (For Monotonously Ascending Timestamps):这是最简单的情况,适用于事件严格按照时间顺序到达的场景。Watermark 的时间戳基本上就是当前所见到的最新事件的时间戳。
- 逻辑 :
watermark_timestamp = latest_event_timestamp - 1
(减 1 是为了确保时间戳为T
的事件能被结束时间为T
的窗口所包含)。
- 逻辑 :
-
有界乱序 (For Bounded Out-of-Orderness) :这是最常见、最实用的策略。需要告诉 Flink,预计事件最大会迟到多久(例如,5秒)。生成器会追踪到目前为止遇到的最大时间戳 (
maxTimestamp
),然后发出一个比maxTimestamp
延迟了指定时间的 Watermark。- 逻辑 :
watermark_timestamp = max_timestamp_seen - max_lateness_delay
。 - 这在 Flink 中的具体实现如下:
java// ... existing code ... public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> { /** 到目前为止遇到的最大时间戳。 */ private long maxTimestamp; /** 此 Watermark 生成器所假定的最大乱序程度。 */ private final long outOfOrdernessMillis; // ... existing code ... @Override public void onEvent(T event, long eventTimestamp, WatermarkOutput output) { maxTimestamp = Math.max(maxTimestamp, eventTimestamp); } @Override public void onPeriodicEmit(WatermarkOutput output) { output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1)); } }
- 逻辑 :
它如何能"保证"之前的事件都已到达?
这是理解 Watermark 的关键:Watermark 是一种启发式机制 (heuristic),而不是一个绝对的物理保证。
一个 Watermark(t)
实际上是开发者对 Flink 系统的一个声明 ,它的意思是:"我(开发者)相信,不会再有时间戳小于等于 t
的事件到来了。"
-
"保证"来自于你的"假设" :这个保证的强度完全取决于你在
WatermarkGenerator
中提供的逻辑是否符合真实数据的特性。- 如果你使用了5秒的"有界乱序"策略,你其实是在告诉 Flink 去假设所有事件的迟到时间都不会超过5秒。
- Flink 会信任这个假设,并使用这个 Watermark 来推进内部的事件时间时钟,从而触发窗口计算等时间相关的操作。
-
如果假设是错的怎么办?(迟到事件) :如果一个时间戳为
t_event
的事件,在系统已经处理了Watermark(t_watermark)
(其中t_event <= t_watermark
) 之后才到达,那么这个事件就被认为是迟到 (late) 的。- 默认情况下,Flink 会丢弃这个迟到的事件。
- 当然,Flink 也提供了处理迟到数据的机制,比如窗口操作中的
allowedLateness()
,它允许你在一定宽限期内继续处理迟到的数据。
-
唯一真正的保证:
Watermark.MAX_WATERMARK
:只有一个特殊的 Watermark 能提供真正的保证。当一个数据源处理完所有数据后(例如文件读完了),它会发出一个时间戳为Long.MAX_VALUE
的最终 Watermark。这个 Watermark 标志着"时间的终结",它能绝对保证此后不会再有任何事件了,从而让 Flink 可以安全地关闭所有未触发的窗口和定时器。java// ... existing code ... /** 标志着事件时间结束的 Watermark。 */ public static final Watermark MAX_WATERMARK = new Watermark(Long.MAX_VALUE); // ... existing code ...