如何在Flink中生成WaterMark


waterMark即水印是Flink中Event Time相关处理的一个重要概念。WaterMark在最大程度上确保了Flink窗口处理中数据的顺序(注意是watermark只是最大程度上确保了数据顺序即一定程度上缓解了数据乱序的问题,某些情况下,数据延迟非常严重即watermark机制也无法确保数据全部进入窗口,关于延迟数据参考allowed lateness机制)。的本文探讨一下如何在Flink中生成WaterMark。
Flink中生成WaterMark需要完成以下4步:

  1. StreamexecationEvironment指定Timecharacterstic
  2. Flink程序中指定Event Time时间戳在数据中的字段信息。
  3. Timestamp Assigning过程,Flink程序通过步骤2所指定额字段抽取对应的Event Time。
  4. 根据定义的WaterMarks生成策略,生成WaterMarks。
    目前Flink支持两种方式指定Timestamps和生成waterMarks:
  5. 在DataStream Source 算子接口的Source Function实现类定义。
  6. 自定义Timestamp Assigner和Watermark Generator生成。

1. 通过SourceFunction接口定义timestamps和Watermarks

在DataStream Source算子中指定EvenTime时间戳,确保数据在进入到Flink系统中就直接指定分配EventTime和Watermark。
SourceFunction接口java定义如下:

public interface SourceFunction<T> extends Function, Serializable {
       /**
         * 启动数据源,实现类可以通过SourceContext类型入参ctx发出元素。           
         * @param ctx 发送元素和接收锁的Context.
         */
        public void run(SourceContext<Tuple2<String, Integer>> ctx) throws Exception ;

        /**
         * 停止DataSource。 大多数源在run(SourceContext ctx)方法内部都会有一个while循环。实现类要确保在调用此方法后,DataSource将能够退出该循环。
         * 一种典型的实现模式是:
         * 在实现类中定义volatile类型的bool变量(volatile boolean isRunning)作为标志,
         * 并在循环中检查该标志。
         * 此外停止DataSource时,执行线程也将被中断(通过Thread.interrupt())。 但可确定的是该线程                          
         * 中断是严格发生在调用此方法之后,因此任何中断处理程序都可以认为此方法已经执行完成。 
         * 一个优雅实践是通过此方法修改所有volatile型的标志,以确保此方法的效果对任何中断处理程序可见
         */
        public void cancel(); 
}

用户实现SourceFunction接口需实现run()方法来实现数据生成逻辑。在run()方法中,调用SourceContext的collectWithTimestamp()方法生成EventTime时间戳,调用emitWatermark()方法生成watermarks。

2. 通过Timestamp Assigner和Watermark Generator实现

2.1 通过自带Timestamp assigner指定Timestamp和watermark

如果用户使用Flink定义的外部数据源连接器,就不能再实现SourceFunction接口来生成流数据及相应的event时间和watermark。这种情况需要通过Timestamp Assigner管理数据流中的Timestamp和watermark。
Timestamp分配器一般跟在data source算子后面指定,也可在后续的算子指定。无论如何指定,要保证Timestamp分配器在第一个依赖时间的operator算子之前指定。如果用户在SourceFunction中定义了Timestamp和watermarks的生成逻辑,同时又使用了Timestamp分配器,后者会覆盖前者定义的逻辑。

Watermarks两种生成方式
Flink定义了两种watermarks生成形式,分别是periodic watermarks和puncuated watermarks。periodic watermarks根据设定时间间隔周期性地生成watermarks,而puncuated watermarks是根据接入数据的数量生成。

2.1.1 periodic watermarks

periodic watermarks根据设定时间间隔周期性地生成watermarks,本生成形式要借助AssignerWithPeriodicWatermarks接口。
Flink系统中有两种模式的Periodic Watermark分配器,分别是升序模和固定时间间隔模式。

(1) 升序模式

Ascending Timestamp分配器会将数据中的Timestamp根据指定字段提取,并用当前的Timestamp作为最小的watermark。这个模式比较适合于事件按顺序生成,没有乱序事件的情况。
升序模式通过AscendingTimestampExtractor的子类定义。AscendingTimestampExtractor的抽象方法extractAscendingTimestamp定义了如何从元素中抽取Timestamp。

**
	 * Extracts the timestamp from the given element. The timestamp must be monotonically increasing.
	 *
	 * @param element The element that the timestamp is extracted from.
	 * @return The new timestamp.
	 */
	public abstract long extractAscendingTimestamp(T element);

子类实现示例

class MyAscendingTimestampExtractor extends AscendingTimestampExtractor<Tuple3<String, Long, Integer>> {

        @Override
        public long extractAscendingTimestamp(Tuple3<String, Long, Integer> element) {
            return element.f1;
        }
    }

DataStrem

DataStream<Tuple3<String, Long, Integer>> ds = 
evn.fromElements(new Tuple3<>("a", 123214L, 2), new Tuple3<>("a", 1237214L, 7),
                new Tuple3<>("a", 123218L, 5), new Tuple3<>("a", 113214L, 0),
                 new Tuple3<>("a", 123114L, 6));

调用DataStream的assignTimestampsAndWatermarks方法指定分配器

ds.assignTimestampsAndWatermarks(new MyAscendingTimestampExtractor());

或者

ds.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<Tuple3<String, Long, Integer>>() {
            @Override
            public long extractAscendingTimestamp(Tuple3<String, Long, Integer> element) {
                return element.f1;
            }
        });
(2) 固定时间间隔

该模式是通过设置固定的时间间隔来指定watermark落后于Timestamp的区间长度。该固定时间间隔就是最长容忍迟到的时间,也即系统最大容忍迟到多长时间内的数据到达系统。
通过创建抽象类BoundedOutOfOrdernessTimestampExtractor的子类实现Timestamp Assigner。BoundedOutOfOrdernessTimestampExtractor定义了extractTimestamp抽象方法用于从元素中抽取Timestamp,其子类要在该方法实现Timestamp抽取策略。

/**
	 * Extracts the timestamp from the given element.
	 *
	 * @param element The element that the timestamp is extracted from.
	 * @return The new timestamp.
	 */
	public abstract long extractTimestamp(T element);

子类实现示例

  class FixedIntervalTimestampExtractor extends BoundedOutOfOrdernessTimestampExtractor<Tuple3<String, Long, Integer>> {
        public FixedIntervalTimestampExtractor(Time maxOutOfOrderness) {
            super(maxOutOfOrderness);
        }
        
        @Override
        public long extractTimestamp(Tuple3<String, Long, Integer> element) {
            return element.f1;
        }
    }

调用DataStream的assignTimestampsAndWatermarks方法指定分配器

 Time interval = Time.milliseconds(300000L);
 ds.assignTimestampsAndWatermarks(new FixedIntervalTimestampExtractor(interval));

或者

Time interval = Time.milliseconds(300000L);
ds.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<Tuple3<String, Long, Integer>>(interval) {
            @Override
            public long extractTimestamp(Tuple3<String, Long, Integer> element) {
                return 0;
            }
        });

其中参数interval指定了最长的300000ms的时延。在extractTimestamp定义了Timestamp抽取逻辑,以上示例中抽取元素的第二个元素做为Event Timestamp。Watermarks创建是根据Timestamp减去固定时间长度生成。如果当前数据中时间大于watermarks时间,则认为是迟到时间。

2.1.2 puncuated watermarks

puncuated watermarks是根据接入数据的数量生成。本生成形式要借助AssignerWithPunctuatedWatermarks接口,后面会讲。

2.2 自定义Timestamp分配器和Watermark生成器

Flink系统自定义的Timestamp分配器不满足需求时,用户可以通过实现AssignerWithPunctuatedWatermarksAssignerWithPeriodicWatermarks两个接口来分别实现自定义的PunctuatedWatermarks和PeriodicWatermarks生成策略。

2.2.1 自定义Periodic Watermarks

Periodic Watermarks根据固定时间间隔,周期性地在Flink系统中分配timestamps和生成watermarks。需要注意的是,需要ExecutionConfig的setAutoWatermarkInterval()方法设置Watermarks产生的时间周期。

long interval = 60000L;//ms
ExecutionConfig executionConfig = new ExecutionConfig();        
executionConfig.setAutoWatermarkInterval(interval);

子类需要实现AssignerWithPeriodicWatermarks接口的getCurrentWatermarkextractTimestamp方法。
extractTimestamp方法定义了抽取Timestamp的逻辑。
getCurrentWatermark方法定义了生成watermark的逻辑。
下面定义了MyPeriodicAssigner ,其生成watermark依赖于curentMaxTimestamp。getCurrentWatermark方法调用时,如果新产生的watermark比当前大,就会覆盖掉当前的watermark,从而实现对watermark数据的更新。

class MyPeriodicAssigner implements AssignerWithPeriodicWatermarks<Tuple3<String, Long, Integer>> {

     //设定30s时延,表示在30s内的数据延时有效,超过30s的数据被认定位迟到事件
     long maxTimeOutOfOrder = 30000L;
     long currentMaxTimestamp = 0;

     @Override
     public long extractTimestamp(Tuple3<String, Long, Integer> element, long previousElementTimestamp) {
         long currentTimestamp = element.f2;
         // 对比当前的事件事件和历史事件时间,将最新的时间戳赋值给currentMaxTimestamp
         currentTimestamp = Math.max(currentTimestamp, currentMaxTimestamp);
         return currentTimestamp;
     }

     @Nullable
     @Override
     public Watermark getCurrentWatermark() {
         // 最大时间减去最大的乱序容忍时长,作为watermark
        return new Watermark(currentMaxTimestamp - maxTimeOutOfOrder);
     }
 }

2.2.2 自定义Punctuated Watermarks

用户除了根据时间周期生成watermark外,更复杂的情况可以自定义Punctuated Watermarks实现生成watermark。自定义Punctuated Watermarks需要实现AssignerWithPunctuatedWatermarks接口,实现的类要实现接口的extractTimestamp和checkAndGetNextWatermark方法。extractTimestamp方法定义了Timestamp的抽取逻辑。checkAndGetNextWatermark方法定义了watermark的生成逻辑。示例如下。

class MyPunctuatedAssigner implements AssignerWithPunctuatedWatermarks<Tuple3<String, Long, Integer>> {
        @Nullable
        @Override
        public Watermark checkAndGetNextWatermark(Tuple3<String, Long, Integer> lastElement, long extractedTimestamp) {
            if (null != lastElement.f2 && lastElement.f2 > 0) return new Watermark(extractedTimestamp);
            return null;
        }

        @Override
        public long extractTimestamp(Tuple3<String, Long, Integer> element, long previousElementTimestamp) {
            return element.f1;
        }
    }

示例中判读元素的第三个字段,如果不为null或者负值则触发生成watermarks,否则不生成。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值