Flink时间处理及Watermarks

1,113 阅读7分钟

时间处理

Flink中支持三种时间:

  • Event Time: 数据生成时间
  • Ingestion Time: 数据进入摄入时间
  • Processing Time: Flink机器时间 Flink支持基于EventTime的时间处理,并且支持Watermarks过滤迟到数据。
    基于EventTime的时间处理能保证事件发生的顺序,因此这种处理方式相对来说会有更多延时更大的性能开销

设置使用哪种时间

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);

在1.12之后setStreamTimeCharacteristic是过期函数,而是在使用窗口的时候可以选择使用哪种时间:

.window(TumblingEventTimeWindows.of(Time.minutes(10)))
.window(SlidingEventTimeWindows.of(Time.minutes(10), Time.minutes(5)))
.window(TumblingProcessingTimeWindows.of(Time.minutes(10)))
.window(SlidingProcessingTimeWindows.of(Time.minutes(10), Time.minutes(5)))

Watermarks

对于数据流,数据不一定按照事件实际发生的顺序处理,所以需要规定多少时间内的数据是有效数据,多久以前的数据是延迟数据不被计算,Flink就是是通过Watermarks实现的。
Watermarks就是时间线,在它之前的数据被认为是过期数据,这之后的数据都是合法数据。
Fink中设置maxOutOfOrderness,表示最大忍受延迟时间,并默认通过下面公式计算Watermarks:

Watermarks = max(EventTime) - maxOutOfOrderness

如何理解Watermarks

  1. 比如使用滑动窗口,窗口长度是10分钟,每5分钟滑动一次窗口,maxOutOfOrderness为10分钟,使用Append模式:

image.png 可以看一下随着数据不断进来,是如何输出结果的,这里我们使用Flink默认的触发计算Watermarks的方式,即当最大的EventTime进入下一个窗口时触发Watermarks计算:

数据Max EventTimeWatermarks结果解释
12:07 dog12:0711:57/生成11:55之前窗口的数据,没有数据
12:08 owl12:0811:58/没有进入新的窗口
12:14 dog12:1412:04/生成12:00之前窗口的数据,没有数据
12:09 cat12:1412:04/没有进入新的窗口
12:15 cat12:1512:05/大于12:15才会进入新的窗口
12:08 dog12:1512:05/没有进入新的窗口,在12:21去计算结果的时候,Watermark还没有更新
12:13 owl12:1512:05/没有进入新的窗口
12:21 owl12:2112:1112:00-12:10 (dog 2),(owl 1),(cat 1)进入新的窗口,生成12:00-12:10前的数据
12:04 donkey12:2112:11/没有进入新的窗口,过期数据
12:26 owl12:2612:1612:05-12:15 (dog 3),(owl 2),(cat 2)进入新的窗口,生成12:05-12:15前的数据
12:17 owl12:2612:16/没有进入新的窗口
12:09 cat12:2612:16/没有进入新的窗口,过期数据
  1. 比如使用滑动窗口,窗口长度是10分钟,每5分钟滑动一次窗口,maxOutOfOrderness为10分钟,使用Update模式:

image.png 可以看一下随着数据不断进来,是如何输出结果的,这里我们使用Flink默认的触发计算Watermarks的方式,即当最大的EventTime进入下一个窗口时触发Watermarks计算:

数据Max EventTimeWatermarks有效数据范围(上一个watermark-maxEventTime)需要更新的窗口结果解释
12:07 dog12:0711:57-12:0711:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15/没有进入新的窗口
12:08 owl12:0811:5811:57-12:0811:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15/没有进入新的窗口
12:14 dog12:1412:0411:58-12:1411:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20[12:00-12:10 (dog 1),(owl 1)],[12:05-12:15 (dog 1),(owl 1)]生成数据
12:09 cat12:1412:0412:04-12:1411:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20/没有进入新的窗口
12:15 cat12:1512:0512:04-12:1512:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25[12:00-12:10 (cat 1)],[12:05-12:15 (dog 2),(cat 1)],[12:10-12:20 (dog 1)]生成数据,并且只更新有变化的数据,自己不被统计进去
12:08 dog12:1512:0512:05-12:1511:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20/没有进入新的窗口
12:13 owl12:1512:0512:05-12:1511:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20/没有进入新的窗口
12:21 owl12:2112:1112:05-12:2112:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30[12:00-12:10 (dog 2)],[12:05-12:15 (dog 3),(owl 2),(cat 2)],[12:10-12:20 (owl 1),(cat 1)]生成数据,并且只更新有变化的数据,自己不被统计进去
12:04 donkey12:2112:1112:11-12:21//没有进入新的窗口,过期数据
12:17 owl12:2112:1112:11-12:2112:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30/没有进入新的窗口
12:26 owl12:2612:1612:11-12:2612:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30,12:25-12:35[12:10-12:20 (owl 2)],[12:15-12:25 (owl 2)]生成数据,并且只更新有变化的数据,自己不被统计进去

代码设置watermarks

在Flink中watermarks可以在数据源上定义也可以在非数据源上定义。
推荐使用在数据源上定义,因为这种方式可以利用数据源的分区等信息更加精确跟踪watermarks,但是这种方式需要数据源接口支持,比如Kafka source。

Kafka source with watermarks

FlinkKafkaConsumer<MyType> kafkaSource = new FlinkKafkaConsumer<>("myTopic", schema, props);
kafkaSource.assignTimestampsAndWatermarks(
        WatermarkStrategy
            //为乱序事件序列设置watermarks
            .forBoundedOutOfOrderness(Duration.ofSeconds(20))
            //为时序单调递增数据设置watermarks
            //.forMonotonousTimestamps()
            
            //optional kafka中可以直接从metadata中获取timestamp
            .withTimestampAssigner((event, timestamp) -> event.getEventTime())
            //optional 某个分区没有一段时间数据时,标记为空闲,防止出错
            .withIdleness(Duration.ofMinutes(1)));
DataStream<MyType> stream = env.addSource(kafkaSource);

非数据源定义watermarks

DataStream<MyEvent> withTimestampsAndWatermarks = stream
        .filter( event -> event.severity() == WARNING )
        .assignTimestampsAndWatermarks(
            WatermarkStrategy
                .forBoundedOutOfOrderness(Duration.ofSeconds(20))
                .withTimestampAssigner((event, timestamp) -> event.getEventTime()));

WatermarkStrategy

1.11版本之后:

  • forBoundedOutOfOrderness: 为乱序事件序列设置watermarks
  • forMonotonousTimestamps: 为时序单调递增数据设置watermarks
  • forGenerator: 自定义watermarks生成器
public class CustomGenerator implements WatermarkGenerator<WatermarksTest.Animal> {
    private final long maxOutOfOrderness = 3500; // 3.5 seconds
    private long currentMaxTimestamp;
    @Override
    public void onEvent(WatermarksTest.Animal event, long eventTimestamp, WatermarkOutput output) {
        currentMaxTimestamp = Math.max(currentMaxTimestamp, eventTimestamp);
    }
    @Override
    public void onPeriodicEmit(WatermarkOutput output) {
        // emit the watermark as current highest timestamp minus the out-of-orderness bound
        output.emitWatermark(new Watermark(currentMaxTimestamp - maxOutOfOrderness - 1));
    }
}

public class CustomWatermarkStrategy implements WatermarkStrategy<WatermarksTest.Animal> {
    @Override
    public WatermarkGenerator<WatermarksTest.Animal> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
        return new CustomGenerator();
    }
}

WatermarkStrategy.forGenerator(new CustomWatermarkStrategy()
               .withTimestampAssigner((event, timestamp) -> event.getEventTime())
               .withIdleness(Duration.ofMinutes(1)));

1.11版本之前:

env.setstreamSTimeCharacteristic(TimeCharacteristic.EventTime);
dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessGenerator())
  1. Periodic: 基于Event time生成WaterMark
public class BoundedOutOfOrdernessGenerator extends AssignerWithPeriodicWatermarks<MyEvent>{
	private final long maxOutOfOrderness = 3500;
	private long currentMaxTimestamp;
	@Override
	public long extractTimestamp(MyEvent element, long previousElementTimestamp){
		long timestamp = element.getCtreaiontime();
		return Math.max(timestamp, currentMaxTimestamp);
	}
	@Override
	public Watermark getCurrentWatermark(){
		return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
	}
}
  1. Punctuated: 基于指定的触发逻辑生成WaterMark
public class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks<MyEvent>{
	@Override
	public long extractTimestamp(MyEvent element, long previousElementTimestamp){
		return element.getCreationTime();
	}
	@Override
	public Watermark checkAndGetNextWatermark(MyEvent lastElement, long extractedTimestamp){
		return lastElement.hasWatermarkMarker() ? new Watermark(extractedTimestamp) : null;
	}
}