Flink对多种时间语义的支持,是它的优势之一;Flink既支持Processing Time,又支持Event Time:
Processing Time 是来模拟我们真实世界的时间,其实就算是处理数据的节点本地时间也不一定就是完完全全的我们真实世界的时间,所以说它是用来模拟真实世界的时间。
而 Event Time 是数据世界的时间,就是我们要处理的数据流世界里面的时间。关于他们的获取方式,Process Time 是通过直接去调用本地机器的时间,而 Event Time 则是根据每一条处理记录所携带的时间戳来判定。
Watermark
Event Time 因为是绑定在每一条的记录上的,由于网络延迟、程序内部逻辑、或者其他一些分布式系统的原因,数据的时间可能会存在一定程度的乱序,比如上图的例子。
解决办法是在整个时间序列里插入一些类似于标志位的一些特殊的处理数据,这些特殊的处理数据叫做 watermark。一个 watermark 本质上就代表了这个 watermark 所包含的 timestamp 数值,表示以后到来的数据已经再也没有小于或等于这个时间的了。
Timestamp 分配和 Watermark 生成
对于用户来讲,可以通过DataStream.assignTimestampsAndWatermarks
方法来生成event time和watermark;示例:
DataStream<MyEvent> stream = ...
DataStream<MyEvent> withTimestampsAndWatermarks =
stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<MyEvent>(Time.seconds(10)) {
@Override
public long extractTimestamp(MyEvent element) {
return element.getCreationTime();
}
});
注意BoundedOutOfOrdernessTimestampExtractor
有一个参数Time.seconds(10),这个配置和数据的“乱序”程度有关;设置得太小,可能会导致数据丢失;设置得太大,可能会导致状态(比如窗口)迟迟没有计算,在内存中积压。
Timer Service
Timer Service可以基于Processing Time或者Event Time设置定时器,可以用于消息的延迟处理/状态的定时清理等操作。
使用条件:
- Keyed Stream
- 使用ProcessFunction
示例:stream.keyBy(...).process(new MyProcessFunction())
CountWithTimeoutFunction
public class CountWithTimeoutFunction
extends KeyedProcessFunction<Tuple, Tuple2<String, String>, Tuple2<String, Long>> {
...
@Override
public void processElement(
Tuple2<String, String> value,
Context ctx,
Collector<Tuple2<String, Long>> out) throws Exception {
...
// set the state's timestamp to the record's assigned event time timestamp
current.lastModified = ctx.timestamp();
// schedule the next timer 60 seconds from the current event time
ctx.timerService().registerEventTimeTimer(current.lastModified + 60000);
}
...
}
注意
- 注册的Timer是占用内存的,如果注册太多Timer会导致内存问题;
- 同一个时间反复注册Timer没有影响(不会重复);
为了避免内存问题,推荐对Timer进行“时间合并“:
long coalescedTime = ((ctx.timestamp() + timeout) / 1000) * 1000;
ctx.timerService().registerProcessingTimeTimer(coalescedTime);