水位线(Watermark)

目录

水位线的定义

1.1有序流中的水位线

1.2乱序流中的水位线

水位线的生成

      2.1生成水位线的总体原则

      2.2水位线的生成策略

      2.3 内置水位线生成器


水位线的定义


       事件时间进展的标记,被称作“水位线”(Watermark)
 具体实现上,水位线可以看作 一条特殊的数据记录,它是插入到数据流中的一个标记点,主要内容就是一个时间戳,用来 指示当前的事件时间。而它插入流中的位置,就应该是在某个数据到来之后;这样就可以从这个数据中提取 时间戳,作为当前水位线的时间戳了。 水位线是一种衡量Event Time进展的机制,用来处理实时数据中的乱序问题的,通常是水位线和窗口结合使用来实现
  • 1.1有序流中的水位线

    1).理想状态(数量小) :数据应该按照 生成的先后顺序流入中, 每条数据生产一个水位线
     2).如果当前 数据量非常大,同时涌进来的数据时间差会非常小(比如几毫秒),往往对处理计算也没有什么影响。所以为了提高效率,一般会 每隔一段时间生成一个水位线
  • 1.2乱序流中的水位线

                    
                    在分布式系统中,数据在节点间传输,会因为网络传输延迟的不确定性,导致顺序发生变化,这就是“ 乱序数据
                        
                    乱序(数据小): 先判断一下时间戳是否比之前的大,否则就不再生成新的水位线(只有数据的时间戳比当前时钟大,才能推动时钟的前进,这是才插入水位线)。
                            
                 水位线(Watermark)_第1张图片
                    乱序(数据大): 保存之前所有数据中的最大时间戳(需要插入水位线时,就直接以它作为时间生成新的水位线)
                    
                     水位线(Watermark)_第2张图片
                    乱序(迟到的数据):为了让窗口正确收集迟到的数据,等上一段时间比如:2秒  水位线代表了当前的时间时间时钟, 可以在数据的时间戳基础上加一些延迟来保证不丢数据(对于乱序流的正确处理非常重要)
                    

水位线的生成


       2.1生成水位线的总体原则

         在流处理中对低延迟和结果正确性的一个权衡机制,用于保证实时处理过程中的数据完整性和准确性。在Flink中,程序员可以在代码中定义水位线的生成策略,通过为流中的数据分配时间戳并生成水位线来指示事件时间。
         首先,完美的水位线是“绝对正确”的,但这是可望不可即的,只能尽量去保证水位线的正确。
         其次,如果希望处理得更快、实时性更强,可以将水位线延迟设得低一些,但这可能导致很多迟到数据会在水位线之后才到达,从而影响结果的正确性。
        因此,需要在低延迟和结果正确性之间进行权衡。
        总之,生成水位线的总体原则是在保证结果正确性的前提下,尽可能地降低延迟。具体实现时需要了解相关领域的知识,并使用适当的方法来生成水位线。

      2.2水位线的生成策略

  在Flink的DataStream API中,
有一个单独用于生成水位线的方法: .assignTimestampsAndWatermarks() ,
它是用来为 流中的数据分配时间戳并生成水位线来指示事件时间 ,具体使用时,直接用DataStream调用该方法即可,与普通的transform方法一样。
DataStream stream = env.addSource(new ClickSource());
DataStream withTimestampsAndWatermarks = stream.assignTimestampsAndWatermarks();
原始的时间戳只是写入日志数据的一个字段,如果不提取出来并明确把它分配给数据,Flink 是无法知道数据真正产生的时间的。当然,有些时候数据源本身就提供了时间戳信息,比如读取 Kafka 时,我们就可以从 Kafka 数据中直接获取时间戳,而不需要单独提取字段分配了。
.assignTimestampsAndWatermarks() 方法需要传入一个 WatermarkStrategy 作为参数,这就是 所 谓 的 “ 水 位 线 生 成 策 略 ” 。 
WatermarkStrategy 中 包 含 了 一 个 “ 时 间 戳 分 配器”TimestampAssigner 和一个“水位线生成器”WatermarkGenerator。
public interface WatermarkStrategy extends TimestampAssignerSupplier,WatermarkGeneratorSupplier{
  @Override
  TimestampAssigner
  createTimestampAssigner(TimestampAssignerSupplier.Context context);
  @Override
  WatermarkGenerator
  createWatermarkGenerator(WatermarkGeneratorSupplier.Context context);
}

TimestampAssigner:主要负责从流中数据元素的某个字段中提取时间戳,并分配给元素。时间戳的分配是生成水位线的基础。

WatermarkGenerator:主要负责按照既定的方式,基于时间戳生成水位线。在WatermarkGenerator 接口中,主要又有两个方法:onEvent()和 onPeriodicEmit()。
onEvent:每个事件(数据)到来都会调用的方法,它的参数有当前事件、时间戳,以及允许发出水位线的一个 WatermarkOutput,可以基于事件做各种操作
onPeriodicEmit:周期性调用的方法,可以由 WatermarkOutput 发出水位线。周期时间为处理时间,可以调用环境配置的.setAutoWatermarkInterval()方法来设置,默认为200ms。

    2.3 内置水位线生成器

      WatermarkStrategy 这个接口是一个生成水位线策略的抽象,让我们可以灵活地实现自己的需求;但看起来有些复杂,如果想要自己实现应该还是比较麻烦的。好在 Flink 充分考虑到了我们的痛苦,提供了内置的水位线生成器(WatermarkGenerator),不仅开箱即用简化了编程,而且也为我们自定义水位线策略提供了模板。
     这两个生成器可以通过调用 WatermarkStrategy 的静态辅助方法来创建。它们都是周期性生成水位线的,分别对应着处理有序流和乱序流的场景。
  • 有序流
             对于有序流,主要特点就是时间戳单调增长(Monotonously Increasing Timestamps),所以永远不会出现迟到数据的问题。这是周期性生成水位线的最简单的场景,直接调用 WatermarkStrategy.forMonotonousTimestamps() 方法就可以实现。简单来说,就是 直接拿当前最大的时间戳作为水位线 就可以了。
stream.assignTimestampsAndWatermarks(
WatermarkStrategy.forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner() {
@Override
public long extractTimestamp(Event element, long recordTimestamp) {
  return element.timestamp;
}
})
);

上面代码中我们调用.withTimestampAssigner()方法,将数据中的 timestamp 字段提取出来,作为时间戳分配给数据元素;然后用内置的有序流水位线生成器构造出了生成策略。这样,提取出的数据时间戳,就是我们处理计算的事件时间。
这里需要注意的是,时间戳和水位线的单位,必须都是毫秒。
  • 乱序流
    由于乱序流中需要等待迟到数据到齐,所以必须设置一个固定量的延迟时间(Fixed Amount of Lateness)。 这时生成水位线的时间戳,就是当前数据流中最大的时间戳减去延迟的结果,相当于把表调慢,当前时钟会滞后于数据的最大时间戳。 调用 WatermarkStrategy. forBoundedOutOfOrderness() 方法就可以实现。这个方法需要传入一个 maxOutOfOrderness 参数,表示“最大乱序程度”,它表示数据流中乱序数据时间戳的最大差值;如果我们能确定乱序程度,那么设置对应时间长度的延迟,就可以等到所有的乱序数据了。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.addSource(new ClickSource())
                // 插入水位线的逻辑
                .assignTimestampsAndWatermarks(
                        // 针对乱序流插入水位线,延迟时间设置为 5s
                        WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))
                                .withTimestampAssigner(new SerializableTimestampAssigner() {
                                    @Override
                                    public long extractTimestamp(Event element, long recordTimestamp) {
                                        return element.timestamp;
                                    }
                                })
                )
                .print();
        env.execute();
       上面代码中,我们同样提取了 timestamp 字段作为时间戳,并且以 5 秒的延迟时间创建了处理乱序流的水位线生成器。
事实上,有序流的水位线生成器本质上和乱序流是一样的,相当于延迟设为 0 的乱序流水位线生成器,两者完全等同:
WatermarkStrategy.forMonotonousTimestamps()
WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(0))

       这里需要注意的是,乱序流中生成的水位线真正的时间戳,其实是 当前最大时间戳 – 延迟时间 – 1,这里的单位是毫秒。为什么要减 1 毫秒呢?我们可以回想一下水位线的特点:时间戳为 t 的水位线,表示时间戳≤t 的数据全部到齐,不会再来了。如果考虑有序流,也就是延迟时间为 0 的情况,那么时间戳为 7 秒的数据到来时,之后其实是还有可能继续来 7 秒的数据的;所以生成的水位线不是 7 秒,而是 6 秒 999 毫秒,7 秒的数据还可以继续来。这一点可以在BoundedOutOfOrdernessWatermarks 的源码中明显地看到:



public void onPeriodicEmit(WatermarkOutput output) {
  output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
}

你可能感兴趣的:(Flink,flink)