flink EventTime中watermark详解

window对数据的操作

flink在对流数据进行窗口操作时,当eventtime在过了窗口时间后会进行关窗操作。然后在实际生产环境中,由于网络等各种各样的原因可能会出现迟到数据,导致数据乱序。如下图所示,圆圈里的数字代表时间,这时候如果对数据进行一个5秒的滚动窗口操作,当第6秒的数据进入时,系统会讲0-5秒的窗口关闭,这时候4秒的数据就会丢失。

遇到这种情况除了调用allowedLateness和侧输出流外,还可以使用watermark。watermark的使用可以处理乱序数据,通过watermark来定义关窗的时间点。

什么是watermark

从watermark字面意思可以理解成水位线,水位线在现实生活中可以理解成表示水位高低的刻度,在flink的语义中watermark也可以理解成一个时间刻度。举一个简单一点的例子,某个航线每天早上10点为首班,每隔半个小时有一班航班起飞,每个起飞点都可以理解成watermark,乘客可以理解成一个个事件。如果所有乘客在起飞之前到达则可以正常乘坐飞机到达目的地,就是有序事件的水位线,如下图所示,1234567都到达了自己的水位线。如果有乘客迟到,飞机不会等他直接飞走,这种情况就比较像乱序事件的水位线,如下图的45一样,如果不对45进行处理,这些数据则会丢失。

有序时间水位线

乱序时间水位线

watermark测试

package com.stanley.flink

/**
  * 一个订单样例类
  * @param timestamp 事件事件
  * @param category 商品类别
  * @param price 价格
  */
case class Order(timestamp:Long,category:String,price:Double)
package com.stanley.flink


import org.apache.flink.api.java.tuple.{Tuple, Tuple1}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

object WatermarkTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //设置为eventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //并行度设置为1
    env.setParallelism(1)
    //创建文本流
    val inputStream:DataStream[String] = env.socketTextStream("node1",9999)
    //转换成order样例类
    val dataStream:DataStream[Order] = inputStream.map(str=>{
      val order = str.split(",")
      new Order(order(0).toLong,order(1),order(2).toDouble)})
    val outputStream:DataStream[Order] = dataStream.
    //调用watermark,最大乱序事件设置为0
      assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Order](Time.seconds(0)) {
      override def extractTimestamp(t: Order): Long = t.timestamp*1000L
    }).keyBy("category").timeWindow(Time.seconds(5)).apply(new MyWindowFunction)
    dataStream.print("Data")
    outputStream.print("Result")
    env.execute()
  }
}

//自定义Windowfunction,对类别进行聚合,返回时间戳为窗口的最大事件
class MyWindowFunction() extends WindowFunction[Order,Order,Tuple,TimeWindow]{
  override def apply(key: Tuple, window: TimeWindow, input: Iterable[Order], out: Collector[Order]): Unit = {
    val timestamp = window.maxTimestamp()
    var sum:Double = 0
    for (elem <- input) {
      sum = elem.price+sum
    }
    //java Tuple转换
    val category:String = key.asInstanceOf[Tuple1[String]].f0
    out.collect(new Order(timestamp,category,sum))
  }
}

输入以下四条数据,当事件事件为390时窗口关闭,对385-389这个时间戳内的事件进行分类聚合,在控制台输出,390的事件为下一个窗口的事件。此时的watermark为390,再次输入390之前的事件,这些数据如果不做处理将丢失

只对390的food进行聚合,389的数据丢失

如何通过watermark处理迟到数据

watermark的函数可以通过传入有一个时间来延迟watermark到来的时间,从源码上就能看到,watermark的是当前的事件时间减去传入的最大乱序时间

修改最大乱序时间,延迟watermark,

再次输入同样的数据,此时控制台没有打印result数据,因为设置了最大乱序时间为1秒,此时的watermark为389

当事件时间变为391时,390的watermark到来,385-389的时间窗口关闭

多任务并行,下游的watermark怎么定义

当上游的多个任务并行,下游的watermark以上游最小的watermark为准

将代码的并行度设置为2

再次输入同样数据,slot2的watermark已经到达390而slot1的watermark还是389,所以下游的watermark还是389,所以没有数据输出

当再次输入392的事件,此时事件轮询到slot1,slot1的watermark变为391,slot2的watermark还是390,下游的watermark为390导致窗口关闭,输入结果数据

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章