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 = ??? }
我有一杯酒,足以慰风尘。