Flink:实时数据处理(5.时间语义和水位线)

1.时间语义

1.1 图示与解释

在这里插入图片描述

  • Event Time:事件创建的时间
  • Ingestion Time:数据进入Flink的时间
  • Processing Time:执行操作算子的本地系统时间,与机器相关。机器时间在分布式系统中又叫做 “墙上时钟”

1.2 设置事件时间

val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

如果要使用 processing time,将 TimeCharacteristic.EventTime 替换为
TimeCharacteristic.ProcessingTIme 就可以了

2.水位线(Watermark)

2.1 watermark概念

  • Watermark是用于处理乱序事件的,正确的处理乱序事件,通常用Watermark机制结合window来实现
  • window的执行是由Watermark触发的,WaterMark代表着timestamp小于其的数据都到达了
  • 只有事件时间才需要水位线
  • 水位线产生公式:水位线 = 系统观察到的最大事件时间 - 最大延迟时间
  • 当水位线超过窗口结束时间时,窗口将不再接收事件,然后触发计算,计算完毕,窗口就被销毁

2.2 watermark 的特点

  • watermark 是一条特殊的数据记录
  • watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而不是在后退
  • watermark 与数据的时间戳相关

2.3 watermark 的引入

  1. Event Time 的使用一定要指定数据源中的时间戳
  2. 可以调用 TimestampAssigner 接口,自定义如何从事件数据中抽取
    时间戳。一般来说,时间戳分配器需要在 source 操作符后马上进行调用:
    因为时间戳分配器看到的元素的顺序应该和 source 操作符产生数据的顺序是一 样的,否则就乱了。任何分区操作都会将元素的顺序打乱,例如并行度改变,keyBy() 操作等等,窄依赖不会,例如map,filter。
  3. 调用 assignTimestampAndWatermarks 方法,传入一个 BoundedOutOfOrdernessTimestampExtractor,就可以指定 watermark
    在这里插入图片描述
    对于排好序的数据,不需要延迟触发,可以只指定时间戳就行了
    在这里插入图片描述

2.3.1 案例:水位线测试

import java.sql.Timestamp

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

/**
 * @Author jaffe
 * @Date 2020/06/11  09:28
 */
object WatermarkTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 应用程序使用事件时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)

    // 注释掉下行代码,默认200ms插入一次水位线,可以设置系统每隔一分钟的机器时间插入一次水位线
    //env.getConfig.setAutoWatermarkInterval(60000)

    val stream = env
      .socketTextStream("hadoop103",9999,'\n')
      .map(line =>{
        val arr = line.split(" ")
        // 第二个元素是时间戳,必须转换成毫秒单位
        (arr(0),arr(1).toLong * 1000)
      })
    // 抽取时间戳和插入水位线
    // 插入水位线的操作一定要紧跟source算子
      .assignTimestampsAndWatermarks(
        // 最大延迟时间设置为5s
        new BoundedOutOfOrdernessTimestampExtractor[(String, Long)](Time.seconds(5)) {
          override def extractTimestamp(t: (String, Long)): Long = t._2
        }
      )
      .keyBy(_._1)
      .timeWindow(Time.seconds(10))
      .process(new MyProcess)

    stream.print()
    env.execute()

  }

  class MyProcess extends ProcessWindowFunction[(String,Long),String,String,TimeWindow]{
    override def process(key: String, context: Context, elements: Iterable[(String, Long)], out: Collector[String]): Unit = {
      out.collect("窗口结束时间为:" + new Timestamp(context.window.getEnd) + " 的窗口中共有 " + elements.size + " 条数据")
    }
  }
}

2.3.2 周期性插入水位线(AssignerWithPeriodicWatermarks)

  • 周期性的生成 watermark:系统会周期性的将 watermark 插入到流中
  • 默认周期是200毫秒,可以使用 ExecutionConfig.setAutoWatermarkInterval() 方法进行设置
  • 升序和前面乱序的处理 BoundedOutOfOrderness ,都是基于周期性 watermark 的

案例演示:

import com.jaffe.day02.{SensorReading, SensorSource}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

/**
 * @Author jaffe
 * @Date 2020/06/11  15:40
 */
object PeriodicInsertWatermarks {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
    val stream = env
      .addSource(new SensorSource)
      .assignTimestampsAndWatermarks(new MyAssigner)
      .keyBy(_.id)
      .timeWindow(Time.seconds(5))
      .process(new MyProcess)

    stream.print()
    env.execute()
  }
  // `BoundedOutOfOrdernessTimestampExtractor`的底层实现
  class MyAssigner extends AssignerWithPeriodicWatermarks[SensorReading]{
    val bound = 1000L// 最大延迟时间
    var maxTs = Long.MinValue + bound// 观察到的最大时间戳

    // 每来一条元素就要调用一次
    override def extractTimestamp(t: SensorReading, l: Long): Long = {
      maxTs = maxTs.max(t.timestamp)
      t.timestamp
    }

    // 产生水位线的函数,默认200ms调用一次
    override def getCurrentWatermark: Watermark = {
      // 水位线 = 观察到的最大时间戳 - 最大延迟时间
      new Watermark(maxTs - bound)
    }
  }

class MyProcess extends ProcessWindowFunction[SensorReading,String,String,TimeWindow]{
    override def process(key: String, context: Context, elements: Iterable[SensorReading], out: Collector[String]): Unit = {

      out.collect(elements.size.toString)
    }
  }
}

2.3.3 不规则插入水位线(AssignerWithPunctuatedWatermarks)

没有时间周期规律,对每条数据进行间断式地生成watermark

class PunctuatedAssigner
 extends AssignerWithPunctuatedWatermarks[SensorReading] {
 val bound: Long = 60 * 1000
 // 每来一条数据就调用一次
 override def checkAndGetNextWatermark(r: SensorReading,
 extractedTS: Long): Watermark = {
 if (r.id == "sensor_1") {//只在id == "sensor_1"时产生水位线,id是随机生成的
 // 抽取的时间戳 - 最大延迟时间
 new Watermark(extractedTS - bound)
 } else {
 null
 }
 }

 // 每来一条数据就调用一次
 override def extractTimestamp(r: SensorReading,
 previousTS: Long): Long = {
 r.timestamp
 }
 }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值