Flink 周练(一)

1、自定义Flink数据源,按照如下要求造数据
数据格式{
    "adsId": 1,
    "userId": 1,
    "provinceName":"山西"
    "timestamp": 1636690000
}
adsId是广告id,取值范围为1-10
userId是用id,取值1-50000
provinceName为省份,取值范围为 北京,山西,山东,河南,河北,上海,福建,广州
timestamp秒时间戳
2、创建Flink程序读取自定义数据源。
3、将读取到的数据封装成样例类。
4、设置时间时间语义,使用timestamp作为时间参考。
5、通过侧流收集迟到数据。(注意在造数据时制造一些迟到数据
6、设置1分钟的滚动窗口,水印为5秒。
7、1分钟内的数据打印到控制台,每条数据包含窗口的开始时间和窗口结束时间。
8、统计每分钟,每个广告的点击次数。
9、统计每分钟,广告点击排名前3的广告信息。
10、将迟到数据保存到kafka。


import com.alibaba.fastjson.JSON
import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.api.common.state.{ListState, ListStateDescriptor}
import org.apache.flink.api.common.typeinfo.{TypeHint, TypeInformation}
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.functions.source.SourceFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
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
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer
import org.apache.flink.streaming.connectors.kafka.KafkaSerializationSchema
import org.apache.kafka.clients.producer.ProducerRecord

import java.nio.charset.StandardCharsets
import java.time.Duration
import java.util.{Date, Properties}
import scala.beans.BeanProperty
import scala.collection.JavaConverters.iterableAsScalaIterableConverter
import scala.util.Random

object Test01 {
  def main(args: Array[String]): Unit = {
    //流处理的上下文环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //设置并行度为1
    env.setParallelism(1)
    //2、创建Flink程序读取自定义数据源。  获取到自定义数据源的数据
    val streamDS: DataStream[ADS] = env.addSource(new MySource)
      //3、将读取到的数据封装成样例类。  将数据进行还原为ADS实体类
      .map(JSON.parseObject(_, classOf[ADS]))
    //5、通过侧流收集迟到数据。(注意在造数据时制造一些迟到数据)
    val tag: OutputTag[ADS] = new OutputTag[ADS]("tag")
    //4、设置时间时间语义,使用timestamp作为时间参考。
    //6、设置1分钟的滚动窗口,水印为5秒。
    val dataDS: DataStream[ADS] = streamDS
      .assignTimestampsAndWatermarks(WatermarkStrategy
      .forBoundedOutOfOrderness[ADS](Duration.ofSeconds(5))
      .withTimestampAssigner(new SerializableTimestampAssigner[ADS] {
        override def extractTimestamp(element: ADS, recordTimestamp: Long): Long = element.timestamp
      }))
    //7、1分钟内的数据打印到控制台,每条数据包含窗口的开始时间和窗口结束时间。
    val windowDS: WindowedStream[ADS, Int, TimeWindow] = dataDS
      .keyBy(_.adsId)                                                     //根据id进行分组
      .window(TumblingEventTimeWindows.of(Time.minutes(1)))      //定义滚动窗口大小
      .sideOutputLateData(tag)                                            //收集侧流数据
    //8、统计每分钟,每个广告的点击次数。     统计每个广告的点击次数  以及窗口信息
    val resDS: DataStream[ADSCount] = windowDS.aggregate(new MyAggreate, new MyWindow)
    //8、统计每分钟,每个广告的点击次数。
    resDS.print("主流:")
    val tagDS: DataStream[ADS] = windowDS
      .aggregate(new MyAggreate, new MyWindow)
      .getSideOutput(tag)
    tagDS.print("测流:")
    //9、统计每分钟,广告点击排名前3的广告信息。
    resDS
      .keyBy(_.end)
      .process(new MyProcess)
      .print("前三:")
    //10、将迟到数据保存到kafka。
    val properties = new Properties()
    properties.setProperty("bootstrap.servers", "hdp1:9092,hdp2:9092,hdp3:9092")

    val serializationSchema = new KafkaSerializationSchema[String] {
      override def serialize(element: String,
                             timestamp: java.lang.Long): ProducerRecord[Array[Byte], Array[Byte]] =
        new ProducerRecord[Array[Byte], Array[Byte]](
          "test",      // target topic
          element.getBytes(StandardCharsets.UTF_8)) // record contents
    }

    val myProducer = new FlinkKafkaProducer[String](
      "test",                  // target topic
      serializationSchema,         // serialization schema
      properties,                  // producer config
      FlinkKafkaProducer.Semantic.EXACTLY_ONCE) // fault-tolerance

    tagDS.map(_.toString).addSink(myProducer)

    //执行流处理
    env.execute()
  }
}
//广告点击排名前3的广告信息
class MyProcess extends KeyedProcessFunction[Long,ADSCount,String] {
  //定义存储状态  创建list集合 存储广告信息
  val list = new ListStateDescriptor[ADSCount]("buffered-elements", TypeInformation.of(new TypeHint[ADSCount]() {}))
  lazy val listState: ListState[ADSCount] = getRuntimeContext.getListState(list)


  override def processElement(i: ADSCount, context: KeyedProcessFunction[Long, ADSCount, String]#Context, collector: Collector[String]): Unit = {
    //将数据放入list
    listState.add(i)
    //创建定时器  在一定时间段内对广告点击次数进行区分
    context.timerService().registerEventTimeTimer(i.end)
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, ADSCount, String]#OnTimerContext, out: Collector[String]): Unit = {
    //对数据进行排序  获取前三
    out.collect(listState.get().asScala.toList.sortBy(-_.count).take(3).toString())
  }
}
//输出格式的样例类
case class ADSCount(start:Long,end:Long,key:Int,count:Int)
//输出格式
//Int Out Key W
class MyWindow extends WindowFunction[Int,ADSCount,Int,TimeWindow] {
  override def apply(key: Int, window: TimeWindow, input: Iterable[Int], out: Collector[ADSCount]): Unit = {
    for (elem <- input) {
      out.collect(ADSCount(window.getStart,window.getEnd,key,elem))
    }
  }
}
//求广告点击次数
//IN ACC OUT
class MyAggreate extends AggregateFunction[ADS,Int,Int] {
  //初始化
  override def createAccumulator(): Int = 0
  //各分支统计个数
  override def add(in: ADS, acc: Int): Int = acc + 1
  //合并分支
  override def merge(acc: Int, acc1: Int): Int = acc + acc1
  //返回结果
  override def getResult(acc: Int): Int = acc
}
//创建样例类
case class ADS(@BeanProperty adsId:Int,@BeanProperty userId:Long,@BeanProperty provinceName:String,@BeanProperty timestamp:Long)
//1、自定义Flink数据源,按照如下要求造数据。(10分)
class MySource extends SourceFunction[String] {
  override def run(sourceContext: SourceFunction.SourceContext[String]): Unit = {
    val arr: Array[String] = Array("北京", "山西", "山东", "河南", "河北", "上海", "福建", "广州")
    var count = 0
    while (true){
      count = count + 1
      val adsId = Random.nextInt(10) + 1 //adsId是广告id,取值范围为1-10
      val userId = Random.nextInt(50000) + 1 //userId是用id,取值1-50000
      val provinceName = arr(Random.nextInt(8))//provinceName为省份,取值范围为 北京,山西,山东,河南,河北,上海,福建,广州
      //判断发送5条数据时
      if (count % 5 == 0){
        val data: String = JSON.toJSON(ADS(adsId, userId, provinceName, new Date().getTime - 20000)).toString
        //注意在造数据时制造一些迟到数据
        sourceContext.collect(data)
      }else{
        val data: String = JSON.toJSON(ADS(adsId, userId, provinceName, new Date().getTime)).toString
        //普通数据
        sourceContext.collect(data)
      }
      //睡眠
      Thread.sleep(1000)
    }
  }
  override def cancel(): Unit = ???
}

 

posted @ 2022-05-05 14:37  御本美琴初号机  阅读(83)  评论(0)    收藏  举报