最近在写一个需要用到定时器的需求,结果遇到了问题。
有时候定时器可以正常触发,有时候却迟迟无法触发,明明已经过去很久时间了,程序却一直没有进入ontimer方法。
针对这种玄学问题,经过上网搜索以及添加日志打印,发现是水位线的问题。
在这里我们先回顾一下水位线的生成规则
Flink 的水位线是基于数据的事件时间生成的。如果某条数据的事件时间远远大于当前水位线,默认情况下,水位线会被立即推到该未来时间。例如:
-
当前水位线为
2023-10-01 10:00:00。 -
一条数据的事件时间为
2062-01-01 00:00:00。 -
水位线会直接更新到
2062-01-01 00:00:00。
这意味着:
-
所有后续数据的事件时间如果小于
2062-01-01 00:00:00,会被认为是“迟到数据”,默认被丢弃(除非设置了允许延迟)。 -
窗口计算会立即触发(如果窗口的结束时间 <= 当前水位线),可能导致错误的结果。
以下是一个验证方案
// 示例数据
DataStream<Event> input = env.fromElements(
new Event("A", 1667200000000L), // 2022-11-01 00:00:00
new Event("B", 32503680000000L) // 2062-01-01 00:00:00
);
input.assignTimestampsAndWatermarks(
WatermarkStrategy
.<Event>forMonotonousTimestamps() // 默认单调递增水位线
.withTimestampAssigner((event, ts) -> event.getEventTime())
);
// 观察 Watermark 输出
input.process(new ProcessFunction<Event, Event>() {
@Override
public void processElement(Event event, Context ctx, Collector<Event> out) {
long watermark = ctx.timerService().currentWatermark();
System.out.println("Current Watermark: " + new Date(watermark));
out.collect(event);
}
});
输出结果:
Current Watermark: -9223372036854775808 (初始值)
Current Watermark: 2062-01-01 00:00:00 (被推到了未来)
而无论是窗口还是定时器的计算,都是基于水位线去触发的。所以如果你的乱序时间不大,可以设置一个合理的乱序时间来处理数据。
像博主这样的,源端数据中有2062年数据的,只能过滤掉这种脏数据或者对数据进行另外的处理了。
以下提供一些解决手段
(1) 数据清洗:过滤或修正异常时间戳
在数据进入 Flink 作业前,通过 ProcessFunction 或 Filter 过滤掉未来的时间戳:
DataStream<Event> cleanedStream = inputStream
.filter(event -> event.getEventTime() < FUTURE_THRESHOLD);
(2) 限制水位线增长:自定义 Watermark 生成器
通过 WatermarkStrategy 限制水位线增长,避免被未来时间戳推得太远:
WatermarkStrategy<Event> strategy = WatermarkStrategy
.<Event>forBoundedOutOfOrderness(Duration.ofDays(1)) // 允许1天延迟
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
.withIdleness(Duration.ofMinutes(5)) // 防止空闲分区问题
.withWatermarkAlignment("alignment-group-1", Duration.ofHours(1)); // Flink 1.17+ 特性
(3) 使用侧输出流处理异常数据
将异常时间戳的数据路由到侧输出流,单独处理:
OutputTag<Event> futureTimeTag = new OutputTag<>("future-time") {};
SingleOutputStreamOperator<Event> mainStream = inputStream
.process(new ProcessFunction<Event, Event>() {
@Override
public void processElement(Event event, Context ctx, Collector<Event> out) {
if (event.getEventTime() > MAX_VALID_TIMESTAMP) {
ctx.output(futureTimeTag, event); // 输出到侧流
} else {
out.collect(event);
}
}
});
// 获取异常数据流
DataStream<Event> futureTimeStream = mainStream.getSideOutput(futureTimeTag);
总结
-
默认情况下,Flink 会信任数据的事件时间,若某条数据的事件时间是未来时间,水位线会被推到该时间。
-
必须主动处理异常时间戳(如清洗、限制水位线、侧输出流),否则会导致作业逻辑错误。
-
建议在生产环境中始终对事件时间做合理性校验。
8552

被折叠的 条评论
为什么被折叠?



