flink1.12版本–滚动窗口水位线

watermark是用于处理乱序事件的,而正确的处理乱序事件,通常用watermark机制结合window来实现。

我们知道,流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、背压等原因,导致乱序的产生(out-of-order或者说late element)。

但是对于late element,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark。

flink 滚动窗口sql 案例 flink滚动窗口不触发_flink 滚动窗口sql 案例


总结:水位线WaterMark提供了一种数据窗口延迟触发的机制。

示例讲解:

Watermark = 进入 Flink 的最大的事件时间(mxtEventTime)— 指定的延迟时间(t)
如果有窗口的停止时间等于或者小于 maxEventTime – t(当时的 warkmark),那么 这个窗口被触发执行。

程序目标:对进20秒内时间窗口的数据统计最大通话时间duration的信息。通过水位线允许数据延迟10秒。

运行流程如下:

输入项:station_4,18600001941,18900003949,success,1617826650000,0
 callTime:2021-04-08 04:17:30 1617826650000 最大延迟10秒。
 watermarkTime:2021-04-08 04:17:20 1617826640000


此时需要窗口结束时间小于watermarkTime才会触发窗口。 注:窗口结束时间是固定的,只有改变watermarkTime才能使公式生效,所以是否触发,由输入数据流中的最大事件时间决定。
Watermark = 进入 Flink 的最大的事件时间(mxtEventTime)— 指定的延迟时间(t)
所以只有当新输入的数据中的最大事件时间>=2021-04-08 04:17:40,才会触发【2021-04-08 04:17:10 ~ 2021-04-08 04:17:30】的窗口计算。

示例:

输入项:station_4,18600001941,18900003949,success,1617826650000,0
callTime:2021-04-08 04:17:30 1617826650000   最大延迟10秒。
watermarkTime:2021-04-08 04:17:20 1617826640000
此时需要窗口结束时间小于watermarkTime才会触发窗口。 注:窗口结束时间是固定的,只有改变watermarkTime才能使公式生效,所以是否触发,由输入数据流中的最大事件时间决定。
所以只有当新输入的数据中的最大时间>=2021-04-08 04:17:30,才会触发【2021-04-08 04:17:10 ~ 2021-04-08 04:17:30】的窗口计算。

输入数据窗口在【2021-04-08 04:17:00 ~ 2021-04-08 04:17:20】窗口的数据,此时最大事件时间未更新,不触发计算。
窗口结束时间:1617826640000
输入项:station_4,18600001941,18900003949,success,1617826630000,0
输入项:station_4,18600001941,18900003949,success,1617826635000,21
输入项:station_4,18600001941,18900003949,success,1617826640000,32
输入项:station_4,18600001941,18900003949,success,1617826645000,34

输入新数据2021-04-08 04:17:31,此时更新最大事件时间,且【2021-04-08 04:17:10 ~ 2021-04-08 04:17:30】的窗口结束时间(1617826650000)<=watermarkTime(1617826660000)
watermarkTime=1617826660000-10000=1617826650000
输入项:station_4,18600001941,18900003949,success,1617826660000,0
触发【2021-04-08 04:17:10 ~ 2021-04-08 04:17:30】窗口的计算。
得到station_4,18600001941,18900003949,success,1617826635000,34
package com.hyr.flink.datastream.eventtime
 
import java.text.SimpleDateFormat
 
import com.hyr.flink.common.StationLog
import com.hyr.flink.common.watermarkgenerator.BoundedOutOfOrdernessGenerator
import org.apache.flink.api.common.eventtime._
import org.apache.flink.api.common.functions.ReduceFunction
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, _}
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
 
 
/** *****************************************************************************
 * @Description: 滚动窗口的无序的数据流处理。 注:WaterMark只是决定数据窗口是否进行延迟触发。
 ******************************************************************************/
object OutOfOrdernessTumblingWaterMarkDemo {
 
   def main(args: Array[String]): Unit = {
    val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 多并行度会自动对齐WaterMark,取最小的WaterMark。避免干扰,将并行度设为1
    streamEnv.setParallelism(1)
    // 周期性的引入WaterMark 间隔100毫秒
    streamEnv.getConfig.setAutoWatermarkInterval(1000)
 
    //读取数据源
    //  val stream: DataStream[StationLog] = streamEnv.addSource(new MyCustomSource)
    val stream: DataStream[StationLog] = streamEnv.socketTextStream("127.0.0.1", 8888)
      .map(line => {
        val arr = line.split(",")
        StationLog(arr(0).trim, arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong)
      })
 
    // 周期性/间断性
    val data = stream.assignAscendingTimestamps(_.callTime).assignTimestampsAndWatermarks(new WatermarkStrategy[StationLog] {
      override def createWatermarkGenerator(context: WatermarkGeneratorSupplier.Context): WatermarkGenerator[StationLog] = {
        // 最长延迟10秒
        new BoundedOutOfOrdernessGenerator(10 * 1000L)
      }
    })
 
    // 有序的情况,为数据流中的元素分配时间戳,并定期创建水印以表示事件时间进度。 设置事件时间
    val result = data
      // 通话成功的记录
      .filter(_.callType.equals("success"))
      .keyBy(_.sid)
      // 每隔10秒统计最近20秒内,每个基站通话时间最长的一次通话记录的基站的id、通话时长、呼叫时间 (毫秒),已经当前发生的时间范围(20秒)  窗口范围左闭右开 延迟的数据会丢掉
      .window(TumblingEventTimeWindows.of(Time.seconds(20)))
      .reduce(new MyReduceWindowFunction(), new ReturnMaxCallTimeStationLogWindowFunction)
 
    result.print()
 
    streamEnv.execute(this.getClass.getName)
  }
 
  /**
   * 增量聚合,找到通话时间最长的记录
   */
  class MyReduceWindowFunction extends ReduceFunction[StationLog] {
    override def reduce(s1: StationLog, s2: StationLog): StationLog = {
      if (s1.duration > s2.duration)
        s1
      else
        s2
    }
  }
 
  /**
   * 返回窗口内最大通话时间的记录
   */
  class ReturnMaxCallTimeStationLogWindowFunction extends WindowFunction[StationLog, String, String, TimeWindow] {
    override def apply(key: String, window: TimeWindow, input: Iterable[StationLog], out: Collector[String]): Unit = {
      val sb = new StringBuilder
      val stationLog = input.iterator.next()
      sb.append("当前时间:").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis())).append("  窗口范围是:").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(window.getStart)).append("---").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(window.getEnd))
        .append("\n")
        .append("value:").append(stationLog)
      out.collect(sb.toString())
    }
  }
 
}
 
 
/**
 * 基站日志
 *
 * @param sid      基站的id
 * @param callOut  主叫号码
 * @param callInt  被叫号码
 * @param callType 呼叫类型
 * @param callTime 呼叫时间 (毫秒)
 * @param duration 通话时长 (秒)
 */
case class StationLog(sid: String, var callOut: String, var callInt: String, callType: String, callTime: Long, duration: Long)
package com.hyr.flink.common.watermarkgenerator
 
import java.text.SimpleDateFormat
 
import com.hyr.flink.common.StationLog
import org.apache.flink.api.common.eventtime.{Watermark, WatermarkGenerator, WatermarkOutput}
 
/**
 * 自定义周期性 Watermark 生成器
 * 该 watermark 生成器可以覆盖的场景是:数据源在一定程度上乱序。
 * 即某个最新到达的时间戳为 t 的元素将在最早到达的时间戳为 t 的元素之后最多 n 毫秒到达。
 */
class BoundedOutOfOrdernessGenerator(_maxOutOfOrderness: Long) extends WatermarkGenerator[StationLog] {
 
  // 最大延迟间隔 n
  private val maxOutOfOrderness: Long = _maxOutOfOrderness
 
  // 最大事件时间 maxTime
  var currentMaxEventTimestamp: Long = _
 
 
  /**
   * 针对每个事件进行调用,使水印生成器可以检查并记住事件时间戳,或者根据事件本身发出水印。
   *
   * @param event          事件数据本身
   * @param eventTimestamp 时间时间错
   * @param output         输出水印
   */
  override def onEvent(event: StationLog, eventTimestamp: Long, output: WatermarkOutput): Unit = {
    println("callTime:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(event.callTime))
    currentMaxEventTimestamp = currentMaxEventTimestamp.max(event.callTime)
    // 记录最大事件时间
    println("currentMaxTimestamp:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(currentMaxEventTimestamp))
    println("currentMaxTimestamp:" + currentMaxEventTimestamp)
    val watermarkTime = currentMaxEventTimestamp - maxOutOfOrderness
    println("watermarkTime:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(watermarkTime))
    println("watermarkTime:" + watermarkTime)
  }
 
  override def onPeriodicEmit(output: WatermarkOutput): Unit = {
    // 发出的 watermark = 当前最大事件时间 - 最大延迟时间
    // Watermark = 进入 Flink 的最大的事件时间(mxtEventTime)— 指定的延迟时间(t)
    // 如果有窗口的停止时间等于或者小于 maxEventTime – t(当时的 warkmark),那么 这个窗口被触发执行。
    val watermarkTime = currentMaxEventTimestamp - maxOutOfOrderness
 
    output.emitWatermark(new Watermark(watermarkTime))
  }
}