全国职业院校技能大赛-大数据应用赛项-数据采集与实时计算-01

子任务一:实时数据采集
1、在主节点使用Flume采集实时数据生成器10050端口的socket数据,将数据存入到Kafka的Topic中(Topic名称为order,分区数为4),使用Kafka自带的消费者消费order(Topic)中的数据,将前2条数据的结果截图粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下;

a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.type  =  netcat 
a1.sources.r1.bind  =  127.0.0.1
a1.sources.r1.port  =  10050 

a1.channels.c1.type = memory

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = order
a1.sinks.k1.kafka.bootstrap.servers = bigdata1:9092,bigdata2:9092,bigdata3:9092
a1.sinks.k1.channel = c1

a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
kafka-console-consumer.sh --bootstrap-server xxx:9092 --topic xxx  --from-beginning   --max-messages 2

2、采用多路复用模式,Flume接收数据注入kafka 的同时,将数据备份到HDFS目录/user/test/flumebackup下,将查看备份目录下的第一个文件的前2条数据的命令与结果截图粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下。

a1.sources = r1
a1.channels = c1 c2
a1.sinks = k1 k2

a1.sources.r1.type  =  netcat 
a1.sources.r1.bind  =  127.0.0.1
a1.sources.r1.port  =  10050 

a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 1000

a1.channels.c2.type = memory
a1.channels.c2.capacity = 1000
a1.channels.c2.transactionCapacity = 1000

a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/test/flumebackup/
a1.sinks.k1.hdfs.writeFormat = Text
a1.sinks.k1.hdfs.fileType = DataStream


a1.sinks.k2.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k2.kafka.topic = order
a1.sinks.k2.kafka.bootstrap.servers = bigdata1:9092,bigdata2:9092,bigdata3:9092

a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2
hdfs dfs -ls /user/test/flumebackup/ | head -n 2

子任务二:使用Flink处理Kafka中的数据
编写Scala代码,使用Flink消费Kafka中Topic为order的数据并进行相应的数据统计计算(订单信息对应表结构order_info,订单详细信息对应表结构order_detail(来源类型和来源编号这两个字段不考虑,所以在实时数据中不会出现),同时计算中使用order_info或order_detail表中create_time或operate_time取两者中值较大者作为EventTime,若operate_time为空值或无此列,则使用create_time填充,允许数据延迟5s,订单状态分别为1001:创建订单、1002:支付订单、1003:取消订单、1004:完成订单、1005:申请退回、1006:退回完成。另外对于数据结果展示时,不要采用例如:1.9786518E7的科学计数法)。
1、使用Flink消费Kafka中的数据,统计商城实时订单实收金额(需要考虑订单状态,若有取消订单、申请退回、退回完成则不计入订单实收金额,其他状态的则累加),将key设置成totalprice存入Redis中。使用redis cli以get key方式获取totalprice值,将结果截图粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下,需两次截图,第一次截图和第二次截图间隔1分钟以上,第一次截图放前面,第二次截图放后面;

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, TimestampAssignerSupplier, WatermarkStrategy}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}

import java.text.SimpleDateFormat
import java.time.Duration

object Exam2_1 {
  def main(args: Array[String]): Unit = {

    // 创建 Flink 的执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    // 创建一个 SimpleDateFormat 对象,用于解析时间戳
    val sdf: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    // 创建用于连接 Redis 的配置对象
    val jedisPoolConfig: FlinkJedisPoolConfig = new FlinkJedisPoolConfig.Builder().setHost("master").build()

    // 创建一个 DataStream,通过连接到套接字并从 "127.0.0.1" 的 8888 端口读取输入数据
    val data: DataStream[String] = env.socketTextStream("127.0.0.1", 8888)

    // 定义一个数组 filterStatus,其中包含要从数据流中过滤掉的一些状态值
    val filterStatus: Array[String] = Array("1003", "1005,1006")

    // 使用 assignTimestampsAndWatermarks 方法为事件时间处理分配时间戳并生成水印
    data.assignTimestampsAndWatermarks(
      watermarkStrategy = WatermarkStrategy
        // 使用 forBoundedOutOfOrderness 策略,生成的水印比观察到的最大时间戳落后 5 秒
        .forBoundedOutOfOrderness(Duration.ofSeconds(5))
        .withTimestampAssigner(new SerializableTimestampAssigner[String] {
          override def extractTimestamp(element: String, recordTimestamp: Long): Long = {
            val row: Array[String] = element.split(",")
            val create_time: String = row(10)
            val operate_time: String = row(11)
            if (operate_time == null || operate_time == "") {
              // 如果 operate_time 为空,则使用 create_time 的时间戳
              sdf.parse(create_time).getTime
            } else {
              // 否则,比较 create_time 和 operate_time,使用较大的时间戳
              if (create_time.compareTo(operate_time) > 0) {
                sdf.parse(create_time).getTime
              } else {
                sdf.parse(operate_time).getTime
              }
            }
          }
        })
    )
      // 对数据流应用过滤操作,将状态值存在 filterStatus 数组中的元素移除
      .filter(r => !filterStatus.contains(r.split(",")(4)))
      // 将每个输入元素映射为一个元组(String, BigDecimal)
      .map(r => {
        ("totalprice", BigDecimal.decimal(r.split(",")(5).toDouble))
      })
      // 将数据流按元组的第一个元素(String)"totalprice" 进行键化
      .keyBy((_: (String, BigDecimal))._1)
      // 对相同键的元素进行累加求和
      .reduce((r1: (String, BigDecimal), r2: (String, BigDecimal)) => {
        (r1._1, r1._2 + r2._2)
      })
      // 使用 RedisSink 将结果元组((String, BigDecimal))写入 Redis 中
      .addSink(new RedisSink[(String, BigDecimal)](jedisPoolConfig, new RedisMapper[(String, BigDecimal)] {
        override def getCommandDescription = {
          new RedisCommandDescription(RedisCommand.SET)
        }

        override def getKeyFromData(t: (String, BigDecimal)) = t._1

        override def getValueFromData(t: (String, BigDecimal)) = t._2.toString
      }))

    // 执行 Flink 应用程序
    env.execute("Exam2_1")
  }
}

2、在任务1进行的同时,使用侧边流,监控若发现order_status字段为退回完成, 将key设置成totalrefundordercount存入Redis中,value存放用户退款消费额。使用redis cli以get key方式获取totalrefundordercount值,将结果截图粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下,需两次截图,第一次截图和第二次截图间隔1分钟以上,第一次截图放前面,第二次截图放后面;

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.extensions._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
import org.apache.flink.util.Collector

import java.text.SimpleDateFormat
import java.time.Duration

object Exam2_1 {
  def main(args: Array[String]): Unit = {

    // 创建 Flink 的执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    // 创建一个 SimpleDateFormat 对象,用于解析时间戳
    val sdf: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    // 创建用于连接 Redis 的配置对象
    val jedisPoolConfig: FlinkJedisPoolConfig = new FlinkJedisPoolConfig.Builder().setHost("master").build()

    // 创建一个 DataStream,通过连接到套接字并从 "127.0.0.1" 的 8888 端口读取输入数据
    val data: DataStream[String] = env.socketTextStream("127.0.0.1", 8888)

    // 定义一个数组 filterStatus,其中包含要从数据流中过滤掉的一些状态值
    val filterStatus: Array[String] = Array("1003", "1005", "1006")

    // 使用 assignTimestampsAndWatermarks 方法为事件时间处理分配时间戳并生成水印
    val processedData: DataStream[(String, BigDecimal)] = data
      .assignTimestampsAndWatermarks(
        WatermarkStrategy
          // 使用 forBoundedOutOfOrderness 策略,生成的水印比观察到的最大时间戳落后 5 秒
          .forBoundedOutOfOrderness(Duration.ofSeconds(5))
          .withTimestampAssigner(new SerializableTimestampAssigner[String] {
            override def extractTimestamp(element: String, recordTimestamp: Long): Long = {
              val row: Array[String] = element.split(",")
              val create_time: String = row(10)
              val operate_time: String = row(11)
              if (operate_time == null || operate_time == "") {
                // 如果 operate_time 为空,则使用 create_time 的时间戳
                sdf.parse(create_time).getTime
              } else {
                // 否则,比较 create_time 和 operate_time,使用较大的时间戳
                if (create_time.compareTo(operate_time) > 0) {
                  sdf.parse(create_time).getTime
                } else {
                  sdf.parse(operate_time).getTime
                }
              }
            }
          })
      )
      // 对数据流应用过滤操作,将状态值存在 filterStatus 数组中的元素移除
      .filter(r => !filterStatus.contains(r.split(",")(4)))
      // 将每个输入元素映射为一个元组(String, BigDecimal)
      .map(r => ("totalprice", BigDecimal.decimal(r.split(",")(5).toDouble)))

    // 使用侧边流进行监控并存入 Redis
    val sideOutputStream: DataStream[(String, BigDecimal)] = processedData
      .process((value, ctx: ProcessFunction[(String, BigDecimal), (String, BigDecimal)]#Context, out: Collector[(String, BigDecimal)]) => {
        val row: Array[String] = value.split(",")
        val order_status: String = row(4)
        val refund_amount: BigDecimal = BigDecimal.decimal(row(6).toDouble)
        if (order_status == "退回完成") {
          // 将退款订单的金额作为侧边输出,存入 Redis
          ctx.output(( "refundOrderAmount", refund_amount))
        } else {
          // 正常订单数据输出到主输出
          out.collect(value)
        }
      })

    // 将主输出流的结果写入 Redis
    processedData
      .keyBy(_._1)
      .reduce((r1: (String, BigDecimal), r2: (String, BigDecimal)) => {
        (r1._1, r1._2 + r2._2)
      })
      .addSink(new RedisSink[(String, BigDecimal)](jedisPoolConfig, new RedisMapper[(String, BigDecimal)] {
        override def getCommandDescription: RedisCommandDescription = {
          new RedisCommandDescription(RedisCommand.SET)
        }

        override def getKeyFromData(t: (String, BigDecimal)): String = t._1

        override def getValueFromData(t: (String, BigDecimal)): String = t._2.toString
      }))

    // 将侧边输出流的结果写入 Redis
    sideOutputStream
      .keyBy(_._1)
      .reduce((r1: (String, BigDecimal), r2: (String, BigDecimal)) => {
        (r1._1, r1._2 + r2._2)
      })
      .addSink(new RedisSink[(String, BigDecimal)](jedisPoolConfig, new RedisMapper[(String, BigDecimal)] {
        override def getCommandDescription: RedisCommandDescription = {
          new RedisCommandDescription(RedisCommand.SET)
        }

        override def getKeyFromData(t: (String, BigDecimal)): String = t._1

        override def getValueFromData(t: (String, BigDecimal)): String = t._2.toString
      }))

    // 执行 Flink 应用程序
    env.execute("Exam2_1")
  }
}

3、在任务1进行的同时,使用侧边流,监控若发现order_status字段为取消订单,将数据存入MySQL数据库shtd_result的order_info表中,然后在Linux的MySQL命令行中根据id降序排序,查询列id、consignee、consignee_tel、final_total_amount、feight_fee,查询出前5条,将SQL语句复制粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下,将执行结果截图粘贴至客户端桌面【Release\任务D提交结果.docx】中对应的任务序号下。
 

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.extensions._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
import org.apache.flink.util.Collector

import java.sql.{Connection, DriverManager, PreparedStatement}
import java.text.SimpleDateFormat
import java.time.Duration

object Exam2_1 {
  def main(args: Array[String]): Unit = {

    // 创建 Flink 的执行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    // 创建一个 SimpleDateFormat 对象,用于解析时间戳
    val sdf: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    // 创建用于连接 Redis 的配置对象
    val jedisPoolConfig: FlinkJedisPoolConfig = new FlinkJedisPoolConfig.Builder().setHost("master").build()

    // 创建一个 DataStream,通过连接到套接字并从 "127.0.0.1" 的 8888 端口读取输入数据
    val data: DataStream[String] = env.socketTextStream("127.0.0.1", 8888)

    // 定义一个数组 filterStatus,其中包含要从数据流中过滤掉的一些状态值
    val filterStatus: Array[String] = Array("1003", "1005", "1006")

    // 使用 assignTimestampsAndWatermarks 方法为事件时间处理分配时间戳并生成水印
    val processedData: DataStream[(String, BigDecimal)] = data
      .assignTimestampsAndWatermarks(
        WatermarkStrategy
          // 使用 forBoundedOutOfOrderness 策略,生成的水印比观察到的最大时间戳落后 5 秒
          .forBoundedOutOfOrderness(Duration.ofSeconds(5))
          .withTimestampAssigner(new SerializableTimestampAssigner[String] {
            override def extractTimestamp(element: String, recordTimestamp: Long): Long = {
              val row: Array[String] = element.split(",")
              val create_time: String = row(10)
              val operate_time: String = row(11)
              if (operate_time == null || operate_time == "") {
                // 如果 operate_time 为空,则使用 create_time 的时间戳
                sdf.parse(create_time).getTime
              } else {
                // 否则,比较 create_time 和 operate_time,使用较大的时间戳
                if (create_time.compareTo(operate_time) > 0) {
                  sdf.parse(create_time).getTime
                } else {
                  sdf.parse(operate_time).getTime
                }
              }
            }
          })
      )
      // 对数据流应用过滤操作,将状态值存在 filterStatus 数组中的元素移除
      .filter(r => !filterStatus.contains(r.split(",")(4)))
      // 将每个输入元素映射为一个元组(String, BigDecimal)
      .map(r => ("totalprice", BigDecimal.decimal(r.split(",")(5).toDouble)))

    // 使用侧边流进行监控并存入 MySQL
    val sideOutputStream: DataStream[(String, String)] = processedData
      .process((value, ctx: ProcessFunction[(String, BigDecimal), (String, BigDecimal)]#Context, out: Collector[(String, String)]) => {
        val row: Array[String] = value.split(",")
        val order_status: String = row(4)
        val order_id: String = row(0)
        if (order_status == "取消订单") {
          // 将取消订单的相关信息作为侧边输出,存入 MySQL
          out.collect((order_id, value))
        } else {
          // 正常订单数据输出到主输出
          out.collect((order_id, ""))
        }
      })

    // 将主输出流的结果写入 Redis
    processedData
      .keyBy(_._1)
      .reduce((r1: (String, BigDecimal), r2: (String, BigDecimal)) => {
        (r1._1, r1._2 + r2._2)
      })
      .addSink(new RedisSink[(String, BigDecimal)](jedisPoolConfig, new RedisMapper[(String, BigDecimal)] {
        override def getCommandDescription: RedisCommandDescription = {
          new RedisCommandDescription(RedisCommand.SET)
        }

        override def getKeyFromData(t: (String, BigDecimal)): String = t._1

        override def getValueFromData(t: (String, BigDecimal)): String = t._2.toString
      }))

    // 将侧边输出流的结果写入 MySQL
    sideOutputStream
      .addSink(new MySQLSink())

    // 执行 Flink 应用程序
    env.execute("Exam2_1")
  }

  // 自定义 MySQL Sink
  class MySQLSink extends RichSinkFunction[(String, String)] {
    private var connection: Connection = _
    private var insertStatement: PreparedStatement = _

    override def open(parameters: Configuration): Unit = {
      // 在 open 方法中建立与 MySQL 数据库的连接,并创建 PreparedStatement 对象
      val url = "jdbc:mysql://localhost:3306/shtd_result"
      val username = "your_username"
      val password = "your_password"
      connection = DriverManager.getConnection(url, username, password)
      val sql = "INSERT INTO order_info (order_id, order_data) VALUES (?, ?)"
      insertStatement = connection.prepareStatement(sql)
    }

    override def invoke(value: (String, String)): Unit = {
      // 在 invoke 方法中执行插入操作,将订单数据存入 MySQL 数据库的 order_info 表中
      insertStatement.setString(1, value._1) // order_id
      insertStatement.setString(2, value._2) // order_data
      insertStatement.execute()
    }

    override def close(): Unit = {
      // 在 close 方法中关闭连接和释放资源
      if (insertStatement != null) {
        insertStatement.close()
      }
      if (connection != null) {
        connection.close()
      }
    }
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值