1、window起始时间的确定

在TimeWindow.java中有如下方法来确定window的起始时间

public static long getWindowStartWithOffset(long timestamp, long offset, long windowSize) {
	return timestamp - (timestamp - offset + windowSize) % windowSize;
}

假设我设置了如下窗口,并将第一条数据的时间戳设置为1547718199。

timeWindow(Time.seconds(15))

那么经过计算,得到起始时间戳为1547718195,第一个窗口就是[ 1547718195, 1547718210 )

1547718199-(1547718199-0+15)%15=1547718195

2、watermark使用详解

object WindowTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //每50ms生成一个watermark
    env.getConfig.setAutoWatermarkInterval(50)

    // 读取数据
    val inputStream = env.socketTextStream("localhost", 7777)

    // 先转换成样例类类型(简单转换操作)
    val dataStream = inputStream
      .map( data => {
        val arr = data.split(",")
        SensorReading(arr(0), arr(1).toLong, arr(2).toDouble)
      } )
//      .assignAscendingTimestamps(_.timestamp * 1000L)    // 升序数据提取时间戳
      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(3)) {
      		override def extractTimestamp(element: SensorReading): Long = element.timestamp * 1000L
    })

    val latetag = new OutputTag[(String, Double, Long)]("late")
    // 每15秒统计一次,窗口内各传感器所有温度的最小值,以及最新的时间戳
    val resultStream = dataStream
      .map( data => (data.id, data.temperature, data.timestamp) )
      .keyBy(_._1)    // 按照二元组的第一个元素(id)分组
      .timeWindow(Time.seconds(15))
        .allowedLateness(Time.seconds(5))
        .sideOutputLateData(latetag)
      	.reduce( (curRes, newData) => (curRes._1, curRes._2.min(newData._2), newData._3) )

    resultStream.getSideOutput(latetag).print("late")
    resultStream.print("result")

    env.execute("window test")

  }
}

// 定义样例类,温度传感器
case class SensorReading( id: String, timestamp: Long, temperature: Double )

上述代码是每15秒统计一次窗口内各传感器所有温度的最小值,以及最新的时间戳。watermark延迟为3秒,允许迟到5秒,之后再迟到的话会被放入侧输出流。

假设我有如下数据

sensor_1,1547718199,35.8
sensor_1,1547718206,32
sensor_1,1547718208,36.2
sensor_1,1547718210,29.7
sensor_1,1547718213,30.9
sensor_1,1547718200,25.6
sensor_1,1547718218,27.2
sensor_1,1547718205,31.7

将这些数据一个个的通过nc输入,直到第五条数据才有结果输出。

我的第一条数据的时间戳是1547718199,经过计算,得到起始时间戳为1547718195,由于窗口大小为15s,所以第一个窗口就是[ 1547718195, 1547718210 )。当数据的时间戳达到窗口的临界点1547718210时就会触发窗口计算,输出结果。那么第四条数据出现的时候就该触发计算操作,但由于设置了watermark延迟时间为3s,所以只有在当前数据时间戳减去3,大于等于1547718210时才会触发计算,即第五条数据才触发计算。由于窗口左闭右开,所以就是计算前三条数据的最小温度和最大时间戳。

flink watermark 时区_数据


当输入第六条数据时又进行了一次计算,这是因为设置了允许迟到5秒。当前最大时间戳是1547718213,watermark就是1547718210,加5秒延迟就是1547718215。也就是只要当前watermark小于1547718215,或者说当前时间戳小于1547718218,就可以接收[ 1547718195, 1547718210 )中的延迟数据。

flink watermark 时区_大数据_02


再添加一条时间戳为1547718218的数据,没有输出,因为这条数据不属于[ 1547718195, 1547718210 )窗口,又没有达到触发下一个窗口[ 1547718210, 1547718225 )计算的条件。

flink watermark 时区_flink watermark 时区_03


最后再添加一条时间戳为1547718205的数据,由于上一条数据的时间戳为1547718218,减去允许延迟的5秒和watermark延迟3秒,就是1547718210 ,达到[ 1547718195, 1547718210 )窗口临界,此窗口关闭。所以这条数据被放入侧输出流,作为迟到数据,后续再处理。

flink watermark 时区_数据_04

生成watermark的时间间隔

源码:

flink watermark 时区_scala_05


默认的watermark生成时间间隔,如果是ProcessingTime为0,其他则为200ms(如EventTime)。如果想自定义时间间隔可以通过如下代码实现:

env.getConfig.setAutoWatermarkInterval(50)

BoundedOutOfOrdernessTimestampExtractor实现了AssignerWithPeriodicWatermarks,所以它也是周期生成watermark的。

疑问:
当时间戳为1547718218的数据进入时,此时的watermark应该是1547718215,减去5秒延迟为1547718210,导致[ 1547718195, 1547718210 )窗口关闭,后面来的1547718205数据就成了迟到数据。

如果将watermark生成的时间间隔设置的很大,当时间戳为1547718218的数据进入时,watermark还没有更新,还是数据1547718213时的watermark 1547718210,减去5秒,为1547718205,此时[ 1547718195, 1547718210 )窗口就不会关闭,那么1547718205数据能正常进入窗口吗?