watermark
什么是watermark?
watermark中文译为水位线,听到这里大家可能就懵逼了,水位线是什么东西?莫慌,听我一一道来
watermark出现原因
在上一节我们说过,processing Time使用的是本地时间节点,所以每一次取到的都是递增有序的,而对于Event Time呢,是事件创建的时间,时间和记录是绑定的。那么问题就来了,由于网络等原因,数据可能会存在乱序,比如前一条数据被堵住,而后一条数据发送成功。如下图所示:
理想的情况下是这样的:
而由于网络等原因,部分数据存在延迟了,变成这样:
显然是存在乱序了,那么该如何处理呢?
Flink设计了一个很聪明的方式,其核心意思就是保证批次有序,这里的批次并不是指多少条为一个批次,而是指另一个概念–窗口,我们稍后会讲到。看一下Flink是如何做到的?
第一步,先将数据划分为一些批次,注意:是以窗口划分,如下图所示,划分为了三部分,装逼术语就叫做离散化
第二步,在批次间插入标志位,可以仔细观察一下标志位数据,均为批次内最大,他表示的就是以后到来的数据再也没有小于这个时间了。所以Watermark的本质是时间戳
如何使用
Talk is cheap. Show me the code,原理哔哔完了,你倒是告诉我代码怎么写呀
-
Watermark的两种生成方式
-
SourceFunction中产生,将Timestamp的分配(也就是上文提到的离散化)和watermark的生成放在上游,同时sourceFunction中也有两个方法生成watermark
-
通过collectwithTimestamp方法发送数据,和调用emitWatermark产生watermark,我们可以看到,调用collectwithTimestamp需要传入两个参数,第一个参数就是数据,第二次参数就是数据对应的时间戳,这样就完成了timestamp的分配,调用emitWatermark生成watermark
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)) } } }
-
-
DataStream API指定,调用assignTimestampsAndWatermarks方法,用于某些sourceFunction不支持的情况,它能够接收不同的timestamp和watermark生成器,说白了就是函数里面参数不同
-
定期生成
val resultData = logData.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[(Long, String, Long)] { val maxOutOfOrderness = 10000L var currentMaxTimestamp: Long = _ override def getCurrentWatermark: Watermark = { new Watermark(currentMaxTimestamp - maxOutOfOrderness) } // 根据数据本身的 Event time 来获取 override def extractTimestamp(element: (Long, String, Long), previousElementTimestamp: Long): Long = { val timestamp = element._1 currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp) timestamp } })
-
标记生成
class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks[SensorReading] { // 1 min in ms val bound: Long = 60 * 1000 override def checkAndGetNextWatermark(r: SensorReading, extractedTS: Long): Watermark = { if (r.id == "sensor_1") { // emit watermark if reading is from sensor_1 new Watermark(extractedTS - bound) } else { // do not emit a watermark null } } override def extractTimestamp(r: SensorReading, previousTS: Long): Long = { // assign record timestamp r.timestamp } }
-
区别:定期指的是定时调用逻辑生成watermark,而标记不是根据时间,而是看到特殊记录表示接下来的数据可能发不过来了,分配timestamp 调用用户实现的watermark方法
-
建议:越靠近源端处理更容易进行判断
-
-