Event Time
使用Event Time首先要设置Time Characteristic
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
watermark
- 生成水印的两大类
- 直接在数据源端产生
- 通过时间戳分配和water 生成器产生
- 示例代码(数据源端产生)
override def run(ctx: SourceContext[MyType]): Unit = {
while (/* condition */) {
val next: MyType = getNext()
ctx.collectWithTimestamp(next, next.eventTimestamp)
if (next.hasWatermarkTime) {
ctx.emitWatermark(new Watermark(next.getWatermarkTime))
}
}
}
-
通过时间戳分配器和water生成器产生。总体上来说又分两种情况
-
AssignerWithperiodicWatermarks 周期性产生 (这也是实际中最常用的)
-
AssignerWithPunctuatedWatermarks 特殊事件产生,意味着根据接入的事件来触发水印
-
类图关系如下
-
-
周期水印实现方式
-
第一种,实现AssignerWithPeriodicWatermarks 接口并实现相关函数
示例: 给了两种自定义实现接口的实例,两者的主要区别是可以看到后者watermark是一个固定值,而前者的水印不一定,准确来说,区别在于,后者假设的是一种固定延迟的情况,具体差别可以看下图
class BoundedOutOfOrdernessGenerator extends AssignerWithPeriodicWatermarks[MyEvent] { val maxOutOfOrderness = 5000L // 3.5 seconds var currentMaxTimestamp: Long = _ override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = { val timestamp = element.getCreationTime() currentMaxTimestamp = max(timestamp, currentMaxTimestamp) timestamp } override def getCurrentWatermark(): Watermark = { // return the watermark as current highest timestamp minus the out-of-orderness bound new Watermark(currentMaxTimestamp - maxOutOfOrderness) } } /** * This generator generates watermarks that are lagging behind processing time by a fixed amount. * It assumes that elements arrive in Flink after a bounded delay. */ class TimeLagWatermarkGenerator extends AssignerWithPeriodicWatermarks[MyEvent] { val maxTimeLag = 5000L // 5 seconds override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = { element.getCreationTime } override def getCurrentWatermark(): Watermark = { // return the watermark as current time minus the maximum time lag new Watermark(System.currentTimeMillis() - maxTimeLag) } }
-
第二种 使用BoundedOutOfOrdernessTimestampExtractor
示例:
应用场景:固定延迟,比如示例中传入的就是10s
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Int](Time.seconds(10)) { override def extractTimestamp(element: Int): Long = { element } })
-
第三种 使用IngestionTimeExtractor
可以看到,使用IngestionTimeExtractor甚至不需要做任何操作,调用即可,实际上是因为底层实现了,如图所示:
示例:
.assignTimestampsAndWatermarks(new IngestionTimeExtractor[Int])
-
第四种 使用AscendingTimestampExtractor
示例:
应用场景:当 kafka 作为数据源时,kafka 的每个 Partition 分区里面时间戳可能是升序或者乱序模式。通常情况,我们会多个 Partition 分区并行处理,我们可以为 kafka 配置水印。
kafka 内部为每个 Partition 分区维护一个水印,并且在流进行 shuffle 时以2.3 并行流中的 Watermarks进行水印合并val kafkaSource = new FlinkKafkaConsumer09[MyType]("myTopic", schema, props) kafkasource.assignTimestampsAndWatermarks(new AscendingTimestampExtractor[Int] { override def extractAscendingTimestamp(element: Int): Long = { element } }) env.addSource(kafkasource)
注意:注意:可以在每个事件上生成水印。但是,由于每个水印都会在下游引起一些计算,因此过多的水印会降低性能。
贴一张图说明吧:
-
-
第五种
-
使用assignAscendingTimestamps 分配Timestamp,实际上
-
示例
val withTimestampsAndWatermarks = stream.assignAscendingTimestamps( _.getCreationTime )
-
实际上底层使用的还是assignTimestampsAndWatermarks,可以看一下源码就能得出结论
-
-
特殊水印的实现
-
从图上可以看出,特殊水印的实现方式有两种,即
- 自定义实现
- 使用AssignerWithPunctuatedWatermarks 类
-
第一种方式
class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks[MyEvent] { override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = { element.getCreationTime } override def checkAndGetNextWatermark(lastElement: MyEvent, extractedTimestamp: Long): Watermark = { if (lastElement.hasWatermarkMarker()) new Watermark(extractedTimestamp) else null } }
-
第二种方式
.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks[Int] { override def checkAndGetNextWatermark(lastElement: Int, extractedTimestamp: Long): Watermark = { ??? } override def extractTimestamp(element: Int, previousElementTimestamp: Long): Long = { ??? } })
参考文章:
https://blog.csdn.net/xiaohulunb/article/details/103148516
https://ci.apache.org/projects/flink/flink-docs-release-1.11/zh/dev/event_timestamps_watermarks.html -