Flink 基于事件时间的窗口计算,和迟到数据的处理

import org.apache.flink.api.scala.createTypeInformation
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.scala.function.ProcessAllWindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
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 TestEventtime {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //设置使用事件时间,及处理数据(划分窗口计算)的 时候使用的时间是数据在产生的时候自带的时间信
    environment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //设置每隔多长时间进行一次水位线计算。水位线代表的就是一个时间点,这个时间点只会随着进来的数据进行增大,不会降低
    //当水位线一确定,就表示水位线之前的数据都以为他们到齐了,一旦达到窗口计算的规则就进行窗口计算,就算有的数据因为各种原因没有到也不会等他了
    //水位线的计算方式是进来事件数据的最大时间减去自定义可允许减去的时间(毫秒)
    //这里设置的这个就是说每经过多久进行一次水位线的计算更新,这里设置 每经过一秒进行更新一次水位线
    environment.getConfig.setAutoWatermarkInterval(1000)
    //设置并行度为1,可以更直观的看到水位线的更新效果。因为如果设置大于一的话会根据并行度中最低的水位线进行窗口的计算,设置为1避免了这些干扰,效果直观
    environment.setParallelism(1)

      //在设置完了事件时间后就进行数据流的读入和计算等操作
    //从数据源读入数据
    val dataStream: DataStream[String] = environment.socketTextStream("192.168.229.10", 9999)
    //传进来的数据必须是:数据  时间戳 这个格式的,把时间戳进行转换
    val dataStream2: DataStream[(String, Long)] = dataStream.map(_.split(" ")).map(v => (v(1), v(2).toLong))

    //在读入数据源之后对数据流进行自定义水位线的提取方式设置、设置窗口的划分、窗口的计算
    val resault: DataStream[String] = dataStream2
      .assignTimestampsAndWatermarks(new MyWWx)//自定义类完成水位线的提取方式设置
      .windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))//设置窗口划分为5秒一个窗口
      .process(new Myprocess)//自定义类设置计算方式

    //对计算完成的结果进行输出
    resault.print()
    //执行
    environment.execute("eventttime")
  }
}
//写一个类继承AssignerWithPeriodicWatermarks类,用来实现自定义的水位线划分方式
//水位线的提取,采用固定频次的方式:每间隔固定的时间提取一次水位线
//参数为输入数据类型
class MyWWx extends AssignerWithPeriodicWatermarks[(String, Long)]{
  //1.由于水位线是根据进来的数据的最大时间时间-允许延迟的最大时间来计算的,所以要先定义出这两个变量
  //设置最大事件时间
  var maxEventTime:Long = _
  //设置允许的最大延迟时间,单位是毫秒
  var maxAllowLateness:Long=2000

  //getCurrentWatermark这个方法是计算水位线的,每间隔固定的时间,执行一次这个方法比如说:程序中设置1秒钟提取一次水位线。这个方法就会每间隔1秒执行一次
  override def getCurrentWatermark: Watermark = new Watermark(maxEventTime-maxAllowLateness)
  //extractTimestamp 每接收一个数据,都会执行一次这个方法去提取每一个事件的时间
  override def extractTimestamp(t: (String, Long), l: Long): Long = {
    //根据进来的数据进行最大事件时间的更新,拿着之前的最大时间和现在进来的这条数据的事件时间比较一下,更新最大时间为他们中的最大值
    maxEventTime=Math.max(maxEventTime,t._2)
    println(s"线程编号是${Thread.currentThread().getId}的线程===>到目前为止,计算出来的水位线是:${maxEventTime-maxAllowLateness}")
    t._2
  }
}
//写一个类继承ProcessAllWindowFunction类,用来实现自定义窗口计算的方式
//三个参数分别为输入数据类型、输出数据类型、窗口类型
class Myprocess extends ProcessAllWindowFunction [(String, Long),String,TimeWindow]{
  override def process(context: Context, elements: Iterable[(String, Long)], out: Collector[String]): Unit = {
    //1.根据上下文获取到窗口对象
    val window: TimeWindow = context.window
    //2.根据窗口对象获取到窗口的信息
    //获取到窗口开始时间
    val start: Long = window.getStart
    //获取到窗口结束时间
    val end: Long = window.getEnd
    //3.由于是滚动窗口下一个窗口的哦开始时上一个窗口的结束,所以是前毖后开
    println(s"窗口的开始以及结束时间是:[${start},${end})")
    //4.将窗口中积累的数据拼接成字符串输出。(由于process是每进来一条数据都会先存起来等够一个窗口里在进行计算)
    out.collect(elements.map(_._1).mkString(","))

  }
}
import org.apache.flink.api.scala.createTypeInformation
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala.{DataStream, OutputTag, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

/**
 * 水位线设置以后,窗口就会进行计算,那么那些迟到的数据,在窗口计算完之后又进来了怎么办
 * Flink中对于这些迟到的数据会有以下三种下场
 * 1.直接扔了,也是Flink中默认的规则
 * 2.设置一个允许迟到的时间,就是说你在我允许的迟到时间内又来了,那我还可以重新把你拿到窗口中再计算一次
 * 3.对于那些连允许的迟到时间内都没有赶到的数据,就不会再那么好心打开窗口再计算一次了,你要是非想看看那些迟到了可以做一个标记,输出一下,看看谁迟到了
 */
object LateEvent {
  def main(args: Array[String]): Unit = {
    //1.设置环境
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    environment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    environment.getConfig.setAutoWatermarkInterval(1000)
    environment.setParallelism(1)

    //设置数据源拿到数据
    val dataSream: DataStream[String] = environment.socketTextStream("192.168.229.10", 9999)
    val dataStream2: DataStream[(String, Long)] = dataSream.map(_.split(" ")).map(v => (v(0), v(1).toLong))

    //创建一个输出标记
    val output = new OutputTag[(String, Long)]("迟到数据")

    val resault: DataStream[String] = dataStream2
      .assignTimestampsAndWatermarks(new MyWWx)
      .windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))
      .allowedLateness(Time.seconds(2)) //设置最大允许迟到2秒
      .sideOutputLateData(output) //设置如果超过了允许迟到了最大时间2秒之后在到的数据就是迟到数据放到上面创建的输出标记里面等会输出
      .process(new Myprocess)

    resault.print()
     //从结果中拿到那些太晚过来的数据,然后进行输出
    val latedata: DataStream[(String, Long)] = resault.getSideOutput(output)
    latedata.print("太晚的数据:")
    environment.execute("laterdata")
  }
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值